@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 CHANGED
@@ -21,56 +21,62 @@ This module expands upon the [Typescript](https://typescriptlang.org) compiler,
21
21
  Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the module provides the primary entry point into the development process.
22
22
 
23
23
  ## CLI
24
- The cli, [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) is a compilation aware entry point, that has the ability to check for active builds, and ongoing watch operations to ensure only one process is building at a time. Within the framework, regardless of mono-repo or not, always builds the entire project. With the efficient caching behavior, this leads to generally a minimal overhead but allows for centralization of all operations.
24
+ The compiler cli, [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js#L7) is a compilation aware entry point, that has the ability to check for active builds, and ongoing watch operations to ensure only one process is building at a time. Within the framework, regardless of mono-repo or not, always builds the entire project. With the efficient caching behavior, this leads to generally a minimal overhead but allows for centralization of all operations.
25
25
 
26
- The CLI supports the following operations:
27
- * `clean` - Removes the output folder, and if `-a` is also passed, will also clean out the compiler folder
28
- * `build` - Will attempt to build the project. If the project is already built, will return immediately. If the project is being built somewhere else, will wait until a build is completed.
29
- * `watch` - If nothing else is watching, will start the watch operation. Otherwise will return immediately.
30
- * `manifest` - Will produce a manifest. If no file is passed in the command line arguments, will output to stdout.
31
- * `<other>` - Will be delegated to the [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") entry point after a successful build.
26
+ The compiler cli supports the following operations:
27
+ * `start|watch` - Run the compiler in watch mode
28
+ * `stop` - Stop the compiler if running
29
+ * `restart` - Restart the compiler in watch mode
30
+ * `build` - Ensure the project is built and upto date
31
+ * `clean` - Clean out the output and compiler caches
32
+ * `info` - Retrieve the compiler information, if running
33
+ * `manifest` - Generate the project manifest
32
34
  In addition to the normal output, the compiler supports an environment variable `TRV_BUILD` that supports the following values: `debug`, `info`, `warn` or `none`. This provides different level of logging during the build process which is helpful to diagnose any odd behaviors. When invoking an unknown command (e.g. `<other>` from above), the default level is `warn`. Otherwise the default logging level is `info`.
33
35
 
34
36
  **Terminal: Sample trv output with debug logging**
35
37
  ```bash
36
- $ TRV_BUILD=debug trv build
38
+ $ TRV_BUILD=debug trvc build
37
39
 
38
- 2029-03-14T04:00:00.618Z [lock ] Acquiring build
39
- 2029-03-14T04:00:00.837Z [precompile ] Started
40
- 2029-03-14T04:00:01.510Z [precompile ] @travetto/terminal Skipped
41
- 2029-03-14T04:00:02.450Z [precompile ] @travetto/manifest Skipped
42
- 2029-03-14T04:00:02.762Z [precompile ] @travetto/transformer Skipped
43
- 2029-03-14T04:00:02.947Z [precompile ] @travetto/compiler Skipped
44
- 2029-03-14T04:00:03.093Z [precompile ] Completed
45
- 2029-03-14T04:00:04.003Z [manifest ] Started
46
- 2029-03-14T04:00:04.495Z [manifest ] Completed
47
- 2029-03-14T04:00:05.066Z [transformers ] Started
48
- 2029-03-14T04:00:05.307Z [transformers ] @travetto/base Skipped
49
- 2029-03-14T04:00:05.952Z [transformers ] @travetto/cli Skipped
50
- 2029-03-14T04:00:06.859Z [transformers ] @travetto/manifest Skipped
51
- 2029-03-14T04:00:07.720Z [transformers ] @travetto/registry Skipped
52
- 2029-03-14T04:00:08.179Z [transformers ] @travetto/schema Skipped
53
- 2029-03-14T04:00:08.588Z [transformers ] Completed
54
- 2029-03-14T04:00:09.493Z [delta ] Started
55
- 2029-03-14T04:00:10.395Z [delta ] Completed
56
- 2029-03-14T04:00:10.407Z [manifest ] Started
57
- 2029-03-14T04:00:10.799Z [manifest ] Wrote manifest @travetto-doc/compiler
58
- 2029-03-14T04:00:11.013Z [manifest ] Completed
59
- 2029-03-14T04:00:11.827Z [compile ] Started action=build changed=
60
- 2029-03-14T04:00:11.894Z [compile ] Skipped
61
- 2029-03-14T04:00:12.133Z [lock ] Releasing build
62
- 2029-03-14T04:00:13.123Z [build ] Successfully built
40
+ 2029-03-14T04:00:00.618Z info [compiler-server] Starting server http://127.0.0.1:29222
41
+ 2029-03-14T04:00:00.837Z debug [compiler-client] Starting watch for events of type "log"
42
+ 2029-03-14T04:00:01.510Z debug [event-stream ] Started event stream
43
+ 2029-03-14T04:00:02.450Z debug [precompile ] Started
44
+ 2029-03-14T04:00:02.762Z debug [compiler-server] Receive request { action: 'event', subAction: 'log' }
45
+ 2029-03-14T04:00:02.947Z debug [precompile ] Skipped @travetto/terminal
46
+ 2029-03-14T04:00:03.093Z debug [precompile ] Skipped @travetto/manifest
47
+ 2029-03-14T04:00:04.003Z debug [precompile ] Skipped @travetto/transformer
48
+ 2029-03-14T04:00:04.495Z debug [precompile ] Skipped @travetto/compiler
49
+ 2029-03-14T04:00:05.066Z debug [precompile ] Completed
50
+ 2029-03-14T04:00:05.307Z debug [manifest ] Started
51
+ 2029-03-14T04:00:05.952Z debug [manifest ] Completed
52
+ 2029-03-14T04:00:06.859Z debug [transformers ] Started
53
+ 2029-03-14T04:00:07.720Z debug [transformers ] Skipped @travetto/base
54
+ 2029-03-14T04:00:08.179Z debug [transformers ] Skipped @travetto/cli
55
+ 2029-03-14T04:00:08.588Z debug [transformers ] Skipped @travetto/manifest
56
+ 2029-03-14T04:00:09.493Z debug [transformers ] Skipped @travetto/registry
57
+ 2029-03-14T04:00:10.395Z debug [transformers ] Skipped @travetto/schema
58
+ 2029-03-14T04:00:10.407Z debug [transformers ] Completed
59
+ 2029-03-14T04:00:10.799Z debug [delta ] Started
60
+ 2029-03-14T04:00:11.013Z debug [delta ] Completed
61
+ 2029-03-14T04:00:11.827Z debug [manifest ] Started
62
+ 2029-03-14T04:00:11.894Z debug [manifest ] Wrote manifest @travetto-doc/compiler
63
+ 2029-03-14T04:00:12.133Z debug [manifest ] Completed
64
+ 2029-03-14T04:00:13.123Z info [compiler-server] State changed: compile-end
65
+ 2029-03-14T04:00:14.014Z debug [compiler-exec ] Skipped
66
+ 2029-03-14T04:00:14.924Z debug [event-stream ] Finished event stream
67
+ 2029-03-14T04:00:15.690Z info [compiler-server] Closing down server
68
+ 2029-03-14T04:00:15.865Z debug [compiler-client] Stopping watch for events of type "log"
63
69
  ```
64
70
 
65
71
  **Terminal: Sample trv output with default log level**
66
72
  ```bash
67
- $ trv build
73
+ $ trvc build
68
74
  ```
69
75
 
70
76
  ## Compilation Architecture
71
77
  The compiler will move through the following phases on a given compilation execution:
72
78
  * `Bootstrapping` - Initial compilation of [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework")'s `support/*.ts` files
73
- * `Lock Management` - Manages cross-process interaction to ensure single compiler
79
+ * `Compiler Server` - Provides a simple HTTP interface to watching compiler file and state changes, and synchronizing multiple processes
74
80
  * `Build Compiler` - Leverages [Typescript](https://typescriptlang.org) to build files needed to execute compiler
75
81
  * `Build Manifest` - Produces the manifest for the given execution
76
82
  * `Build Transformers` - Leverages [Typescript](https://typescriptlang.org) to compile all transformers defined in the manifest
@@ -80,12 +86,4 @@ The compiler will move through the following phases on a given compilation execu
80
86
  * `Invoke Compiler` - Run [Typescript](https://typescriptlang.org) compiler with the aforementioned enhancements
81
87
 
82
88
  ### Bootstrapping
83
- Given that the framework is distributed as [Typescript](https://typescriptlang.org) only files, there is a bootstrapping problem that needs to be mitigated. The [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) entrypoint, along with a small context utility in [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching") are the only [Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) files needed to run the project. The [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) entry point will compile `@travetto/compiler/support/*` files as the set that is used at startup. These files are also accessible to the compiler as they get re-compiled after the fact.
84
-
85
- ### Lock Management
86
- The compiler supports invocation from multiple locations at the same time, and provides a layer of orchestration to ensure a single process is building at a time. For a given project, there are four main states:
87
- * No Watch - Building
88
- * Watch - No Build
89
- * Watch - Building
90
- * Inactive / Stale
91
- Depending on what state the project is in (depending on various processes), will influence what the supporting tooling should do. [LockManager](https://github.com/travetto/travetto/tree/main/module/compiler/support/lock.ts#L25) represents the majority of the logic for tracking various states, and informing what action should happen when in the above states.
89
+ Given that the framework is distributed as [Typescript](https://typescriptlang.org) only files, there is a bootstrapping problem that needs to be mitigated. The [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js#L7) entrypoint, along with a small context utility in [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching") are the only [Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) files needed to run the project. The [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js#L7) entry point will compile `@travetto/compiler/support/*` files as the set that is used at startup. These files are also accessible to the compiler as they get re-compiled after the fact.
@@ -1,21 +1,18 @@
1
- #!/usr/bin/env node
2
-
3
1
  // @ts-check
2
+
4
3
  import fs from 'fs/promises';
5
4
  import path from 'path';
6
5
  import { createRequire } from 'module';
7
6
 
8
7
  import { getManifestContext } from '@travetto/manifest/bin/context.js';
9
8
 
10
- const VALID_OPS = { watch: 'watch', build: 'build', clean: 'clean', manifest: 'manifest' };
9
+ const COMPILER_FILES = [...['entry.trvc', 'log', 'queue', 'server/client', 'server/runner', 'server/server', 'setup', 'util'].map(x => `support/${x}.ts`), 'package.json'];
11
10
 
12
- const COMPILER_FILES = [...['launcher', 'transpile', 'lock', 'log', 'lock-pinger'].map(x => `support/${x}.ts`), 'package.json'];
11
+ /** @typedef {import('@travetto/manifest/src/types').ManifestContext} Ctx */
12
+ /** @typedef {import('@travetto/compiler/support/types').EntryOp} EntryOp */
13
13
 
14
- /**
15
- * @param {import('@travetto/manifest').ManifestContext} ctx
16
- * @return {Promise<import('@travetto/compiler/support/launcher').launch>}
17
- */
18
- const $getLauncher = async (ctx) => {
14
+ /** @return {Promise<import('@travetto/compiler/support/entry.trvc').main>} */
15
+ const $getEntry = async (/** @type {Ctx} */ ctx) => {
19
16
  const tsconfigFile = path.resolve(ctx.workspacePath, 'tsconfig.json');
20
17
  if (!(await fs.stat(tsconfigFile).catch(() => undefined))) {
21
18
  await fs.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }), 'utf8');
@@ -38,7 +35,7 @@ const $getLauncher = async (ctx) => {
38
35
  if (/[.]tsx?$/.test(file)) {
39
36
  const content = ts.transpile(
40
37
  text,
41
- { target: ts.ScriptTarget.ES2020, module, esModuleInterop: true, allowSyntheticDefaultImports: true }
38
+ { target: ts.ScriptTarget.ES2022, module, esModuleInterop: true, allowSyntheticDefaultImports: true }
42
39
  )
43
40
  .replace(/^((?:im|ex)port .*from '[.][^']+)(')/mg, (_, a, b) => `${a}.js${b}`)
44
41
  .replace(/^(import [^\n]*from '[^.][^\n/]+[/][^\n/]+[/][^\n']+)(')/mg, (_, a, b) => `${a}.js${b}`);
@@ -53,23 +50,21 @@ const $getLauncher = async (ctx) => {
53
50
  files.push(target);
54
51
  }
55
52
 
56
- try { return require(files[0]).launch; }
57
- catch { return import(files[0]).then(x => x.launch); }
53
+ try { return require(files[0]).main; }
54
+ catch { return import(files[0]).then(x => x.main); }
58
55
  };
59
56
 
60
- (async () => {
61
- const ctx = await getManifestContext();
62
- const [op, args] = [VALID_OPS[process.argv[2]], process.argv.slice(3)];
63
-
64
- if (op === 'clean') {
65
- const folders = process.argv.find(x => x === '--all' || x === '-a') ? [ctx.outputFolder, ctx.compilerFolder] : [ctx.outputFolder];
66
- for (const f of folders) {
67
- await fs.rm(path.resolve(ctx.workspacePath, f), { force: true, recursive: true });
68
- }
69
- return console.log(`Cleaned ${ctx.workspacePath}: [${folders.join(', ')}]`);
70
- }
71
-
72
- const rootCtx = ctx.monoRepo ? await getManifestContext(ctx.workspacePath) : ctx;
57
+ async function $compile(/** @type {Ctx} ctx*/ ctx, /** @type {EntryOp} op */ op) {
58
+ const rootCtx = await (ctx.monoRepo ? getManifestContext(ctx.workspacePath) : ctx);
59
+ return (await $getEntry(ctx))(ctx, rootCtx, op, process.argv.slice(3));
60
+ }
73
61
 
74
- return (await $getLauncher(ctx))(ctx, rootCtx, op, args);
75
- })();
62
+ /**
63
+ * @template T
64
+ * @param {(ctx: Ctx, compile: typeof $compile) => Promise<T>} fn
65
+ * @returns {Promise<T>}
66
+ */
67
+ export async function withContext(fn) {
68
+ const ctx = await getManifestContext();
69
+ return fn(ctx, $compile);
70
+ }
package/bin/trvc.js ADDED
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+
3
+ // @ts-check
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+
7
+ import { withContext } from './common.js';
8
+
9
+ /** @typedef {import('@travetto/manifest/src/types').ManifestContext} Ctx */
10
+
11
+ const stop = async (/** @typedef {Ctx} */ ctx) => {
12
+ if (await fetch(`${ctx.compilerUrl}/stop`).then(v => v.ok, () => false)) {
13
+ console.log(`Stopped server ${ctx.workspacePath}: [${ctx.compilerUrl}]`);
14
+ } else {
15
+ console.log(`Server not running ${ctx.workspacePath}: [${ctx.compilerUrl}]`);
16
+ }
17
+ };
18
+
19
+ const info = async (/** @typedef {Ctx} */ctx) => console.log(
20
+ JSON.stringify(await fetch(ctx.compilerUrl).then(v => v.json(), () => undefined) ?? { state: 'Server not running' }, null, 2)
21
+ );
22
+
23
+ const clean = async (/** @typedef {Ctx} */ctx) => {
24
+ const folders = [ctx.outputFolder, ctx.compilerFolder];
25
+ if (await fetch(`${ctx.compilerUrl}/clean`).then(v => v.ok, () => false)) {
26
+ return console.log(`Clean triggered ${ctx.workspacePath}:`, folders);
27
+ } else {
28
+ await Promise.all(folders.map(f => fs.rm(path.resolve(ctx.workspacePath, f), { force: true, recursive: true })));
29
+ return console.log(`Cleaned ${ctx.workspacePath}:`, folders);
30
+ }
31
+ };
32
+
33
+ const help = () => [
34
+ 'npx trvc [command]',
35
+ '',
36
+ 'Available Commands:',
37
+ ' * start|watch - Run the compiler in watch mode',
38
+ ' * stop - Stop the compiler if running',
39
+ ' * restart - Restart the compiler in watch mode',
40
+ ' * build - Ensure the project is built and upto date',
41
+ ' * clean - Clean out the output and compiler caches',
42
+ ' * info - Retrieve the compiler information, if running',
43
+ ' * manifest - Generate the project manifest',
44
+ ].join('\n');
45
+
46
+ withContext(async (ctx, compile) => {
47
+ const op = process.argv[2];
48
+
49
+ switch (op) {
50
+ case 'restart': return stop(ctx).then(() => compile(ctx, 'watch'));
51
+ case 'stop': return stop(ctx);
52
+ case 'info': return info(ctx);
53
+ case 'clean': return clean(ctx);
54
+ case 'watch':
55
+ case 'start': return compile(ctx, 'watch');
56
+ case 'build':
57
+ case 'manifest': return compile(ctx, op);
58
+ case undefined:
59
+ case 'help': return console.log(`\n${help()}\n`);
60
+ default: console.error(`Unknown trvc operation: ${op}\n`); return console.error(help());
61
+ }
62
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "3.3.1",
3
+ "version": "3.4.0-rc.0",
4
4
  "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "main": "__index__.ts",
25
25
  "bin": {
26
- "trv": "bin/trv.js"
26
+ "trvc": "bin/trvc.js"
27
27
  },
28
28
  "repository": {
29
29
  "url": "https://github.com/travetto/travetto.git",
@@ -31,12 +31,13 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@parcel/watcher": "^2.3.0",
34
- "@travetto/manifest": "^3.3.1",
35
- "@travetto/terminal": "^3.3.1",
36
- "@travetto/transformer": "^3.3.1"
34
+ "@travetto/manifest": "^3.4.0-rc.0",
35
+ "@travetto/terminal": "^3.4.0-rc.0",
36
+ "@travetto/transformer": "^3.4.0-rc.0",
37
+ "@types/node": "^20.8.9"
37
38
  },
38
39
  "peerDependencies": {
39
- "@travetto/cli": "^3.3.3"
40
+ "@travetto/cli": "^3.4.0-rc.0"
40
41
  },
41
42
  "peerDependenciesMeta": {
42
43
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -2,7 +2,6 @@ import { install } from 'source-map-support';
2
2
  import ts from 'typescript';
3
3
  import fs from 'fs/promises';
4
4
 
5
- import { GlobalTerminal, TerminalProgressEvent } from '@travetto/terminal';
6
5
  import { ManifestModuleUtil, RootIndex } from '@travetto/manifest';
7
6
 
8
7
  import { CompilerUtil } from './util';
@@ -10,6 +9,7 @@ import { CompilerState } from './state';
10
9
  import { CompilerWatcher } from './watch';
11
10
  import { Log } from './log';
12
11
  import { CompileEmitError, CompileEmitEvent, CompileEmitter } from './types';
12
+ import { EventUtil } from './event';
13
13
 
14
14
  /**
15
15
  * Compilation support
@@ -39,7 +39,6 @@ export class Compiler {
39
39
  this.#watch = watch;
40
40
  }
41
41
 
42
-
43
42
  /**
44
43
  * Compile in a single pass, only emitting dirty files
45
44
  */
@@ -79,9 +78,10 @@ export class Compiler {
79
78
  yield { file: imp, i: i += 1, err, total: files.length };
80
79
  if ((Date.now() - lastSent) > 50) { // Limit to 1 every 50ms
81
80
  lastSent = Date.now();
82
- process.send?.({ type: 'status', total: files.length, idx: i });
81
+ EventUtil.sendEvent('progress', { total: files.length, idx: i, message: imp, operation: 'compile' });
83
82
  }
84
83
  }
84
+ EventUtil.sendEvent('progress', { total: files.length, idx: files.length, message: 'Complete', operation: 'compile', complete: true });
85
85
  Log.debug(`Compiled ${i} files`);
86
86
  }
87
87
 
@@ -89,11 +89,12 @@ export class Compiler {
89
89
  * Run the compiler
90
90
  */
91
91
  async run(): Promise<void> {
92
- await GlobalTerminal.init();
93
92
  install();
94
93
 
95
94
  Log.debug('Compilation started');
96
95
 
96
+ EventUtil.sendEvent('state', { state: 'init', extra: { pid: process.pid } });
97
+
97
98
  if (process.send) {
98
99
  process.on('disconnect', () => process.exit(0));
99
100
  }
@@ -103,18 +104,16 @@ export class Compiler {
103
104
 
104
105
  Log.debug('Compiler loaded');
105
106
 
106
- const resolveEmittedFile = ({ file, total, i, err }: CompileEmitEvent): TerminalProgressEvent => {
107
- if (err) {
108
- failed = true;
109
- console.error(CompilerUtil.buildTranspileError(file, err));
110
- }
111
- return { idx: i, total, text: `Compiling [%idx/%total] -- ${file}` };
112
- };
113
-
114
- process.send?.({ type: 'start' });
107
+ EventUtil.sendEvent('state', { state: 'compile-start' });
115
108
 
116
109
  if (this.#dirtyFiles.length) {
117
- await GlobalTerminal.trackProgress(this.emit(this.#dirtyFiles, emitter), resolveEmittedFile, { position: 'bottom', minDelay: 50 });
110
+ for await (const ev of this.emit(this.#dirtyFiles, emitter)) {
111
+ if (ev.err) {
112
+ failed = true;
113
+ const compileError = CompilerUtil.buildTranspileError(ev.file, ev.err);
114
+ EventUtil.sendEvent('log', { level: 'error', message: compileError.toString(), time: Date.now() });
115
+ }
116
+ }
118
117
  if (failed) {
119
118
  Log.debug('Compilation failed');
120
119
  process.exit(1);
@@ -127,28 +126,45 @@ export class Compiler {
127
126
  await emitter(resolved, true);
128
127
  }
129
128
 
130
- process.send?.({ type: 'complete' });
129
+ EventUtil.sendEvent('state', { state: 'compile-end' });
131
130
 
132
131
  if (this.#watch) {
133
132
  Log.info('Watch is ready');
134
- for await (const { file, action } of CompilerWatcher.watch(this.#state)) {
135
- if (action === 'restart') {
136
- Log.info(`Triggering restart due to change in ${file}`);
137
- process.send?.({ type: 'restart' });
133
+
134
+ EventUtil.sendEvent('state', { state: 'watch-start' });
135
+
136
+ for await (const ev of CompilerWatcher.watch(this.#state)) {
137
+ if (ev.action === 'reset') {
138
+ Log.info(`Triggering reset due to change in ${ev.file}`);
139
+ EventUtil.sendEvent('state', { state: 'reset' });
138
140
  return;
139
141
  }
140
- const module = file.split('node_modules/')[1];
142
+ const { action, entry } = ev;
141
143
  if (action !== 'delete') {
142
- const err = await emitter(file, true);
144
+ const err = await emitter(entry.input, true);
143
145
  if (err) {
144
- Log.info('Compilation Error', CompilerUtil.buildTranspileError(file, err));
146
+ Log.info('Compilation Error', CompilerUtil.buildTranspileError(entry.input, err));
145
147
  } else {
146
- Log.info(`Compiled ${module}`);
148
+ Log.info(`Compiled ${entry.source}`);
147
149
  }
148
150
  } else {
149
- Log.info(`Removed ${module}`);
151
+ // Remove output
152
+ Log.info(`Removed ${entry.source}, ${entry.output}`);
153
+ await fs.rm(entry.output!, { force: true }); // Ensure output is deleted
150
154
  }
155
+
156
+ // Send change events
157
+ EventUtil.sendEvent('change', {
158
+ action: ev.action,
159
+ time: Date.now(),
160
+ file: ev.file,
161
+ folder: ev.folder,
162
+ output: ev.entry.output!,
163
+ module: ev.entry.module.name
164
+ });
151
165
  }
166
+
167
+ EventUtil.sendEvent('state', { state: 'watch-end' });
152
168
  }
153
169
  }
154
170
  }
package/src/event.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { CompilerServerEvent, CompilerServerEventType } from '../support/types';
2
+
3
+ export class EventUtil {
4
+ static sendEvent<K extends CompilerServerEventType, T extends CompilerServerEvent & { type: K }>(type: K, payload: T['payload']): void {
5
+ process.send?.({ type, payload });
6
+ }
7
+ }
@@ -0,0 +1,104 @@
1
+ import fs from 'fs/promises';
2
+
3
+ import { IndexedModule, ManifestContext, ManifestModuleUtil, RootIndex, path } from '@travetto/manifest';
4
+
5
+ import { AsyncQueue } from '../../support/queue';
6
+
7
+ export type WatchEvent<T = {}> =
8
+ ({ action: 'create' | 'update' | 'delete', file: string, folder: string } & T) |
9
+ { action: 'reset', file: string };
10
+
11
+ const CREATE_THRESHOLD = 50;
12
+ const VALID_TYPES = new Set(['ts', 'typings', 'js', 'package-json']);
13
+ type ToWatch = { file: string, actions: string[] };
14
+
15
+ /** Watch file for reset */
16
+ async function watchForReset(q: AsyncQueue<WatchEvent>, root: string, files: ToWatch[], signal: AbortSignal): Promise<void> {
17
+ const watchers: Record<string, { folder: string, files: Map<string, (ToWatch & { name: string, actionSet: Set<string> })> }> = {};
18
+ // Group by base path
19
+ for (const el of files) {
20
+ const full = path.resolve(root, el.file);
21
+ const folder = path.dirname(full);
22
+ const tgt = { ...el, name: path.basename(el.file), actionSet: new Set(el.actions) };
23
+ const watcher = (watchers[folder] ??= { folder, files: new Map() });
24
+ watcher.files.set(tgt.name, tgt);
25
+ }
26
+
27
+ // Fire them all off
28
+ Object.values(watchers).map(async (watcher) => {
29
+ for await (const ev of fs.watch(watcher.folder, { persistent: true, encoding: 'utf8', signal })) {
30
+ const toWatch = watcher.files.get(ev.filename!);
31
+ if (toWatch) {
32
+ const stat = await fs.stat(path.resolve(root, ev.filename!)).catch(() => undefined);
33
+ const action = !stat ? 'delete' : ((Date.now() - stat.ctimeMs) < CREATE_THRESHOLD) ? 'create' : 'update';
34
+ if (toWatch.actionSet.has(action)) {
35
+ q.add({ action: 'reset', file: ev.filename! });
36
+ }
37
+ }
38
+ }
39
+ });
40
+ }
41
+
42
+ /** Watch recursive files for a given folder */
43
+ async function watchFolder(ctx: ManifestContext, q: AsyncQueue<WatchEvent>, src: string, target: string, signal: AbortSignal): Promise<void> {
44
+ const lib = await import('@parcel/watcher');
45
+ const ignore = [
46
+ 'node_modules', '**/.trv',
47
+ ...((!ctx.monoRepo || src === ctx.workspacePath) ? [ctx.compilerFolder, ctx.outputFolder, ctx.toolFolder] : []),
48
+ ...(await fs.readdir(src)).filter(x => x.startsWith('.'))
49
+ ];
50
+
51
+ const cleanup = await lib.subscribe(src, async (err, events) => {
52
+ if (err) {
53
+ console.error('Watch Error', err);
54
+ }
55
+ for (const ev of events) {
56
+ const finalEv = { action: ev.type, file: path.toPosix(ev.path), folder: target };
57
+ if (ev.type !== 'delete') {
58
+ const stats = await fs.stat(finalEv.file);
59
+ if ((Date.now() - stats.ctimeMs) < CREATE_THRESHOLD) {
60
+ ev.type = 'create'; // Force create on newly stated files
61
+ }
62
+ }
63
+
64
+ if (ev.type === 'delete' && finalEv.file === src) {
65
+ return q.close();
66
+ }
67
+
68
+ const matches = !finalEv.file.includes('/.') && VALID_TYPES.has(ManifestModuleUtil.getFileType(finalEv.file));
69
+ if (matches) {
70
+ q.add(finalEv);
71
+ }
72
+ }
73
+ }, { ignore });
74
+ signal.addEventListener('abort', () => cleanup.unsubscribe());
75
+ }
76
+
77
+ /** Watch files */
78
+ export async function* fileWatchEvents(manifest: ManifestContext, modules: IndexedModule[], signal: AbortSignal): AsyncIterable<WatchEvent> {
79
+ const q = new AsyncQueue<WatchEvent>(signal);
80
+
81
+ for (const m of modules.filter(x => !manifest.monoRepo || x.sourcePath !== manifest.workspacePath)) {
82
+ watchFolder(manifest, q, m.sourcePath, m.sourcePath, signal);
83
+ }
84
+
85
+ // Add monorepo folders
86
+ if (manifest.monoRepo) {
87
+ const mono = modules.find(x => x.sourcePath === manifest.workspacePath)!;
88
+ for (const folder of Object.keys(mono.files)) {
89
+ if (!folder.startsWith('$')) {
90
+ watchFolder(manifest, q, path.resolve(mono.sourcePath, folder), mono.sourcePath, signal);
91
+ }
92
+ }
93
+ }
94
+
95
+ watchForReset(q, RootIndex.manifest.workspacePath, [
96
+ { file: RootIndex.manifest.outputFolder, actions: ['delete'] },
97
+ { file: RootIndex.manifest.compilerFolder, actions: ['delete'] },
98
+ { file: RootIndex.manifest.toolFolder, actions: ['delete'] },
99
+ { file: 'package-lock.json', actions: ['delete', 'update', 'create'] },
100
+ { file: 'package.json', actions: ['delete', 'update', 'create'] }
101
+ ], signal);
102
+
103
+ yield* q;
104
+ }
package/src/log.ts CHANGED
@@ -1,18 +1,17 @@
1
- import util from 'util';
1
+ import type { CompilerLogLevel } from '../support/types';
2
+ import { EventUtil } from './event';
2
3
 
3
- import type { CompilerLogEvent } from '../support/log';
4
-
5
- function log(level: 'info' | 'debug', message: string, ...args: unknown[]): void {
6
- if (process.send) {
7
- const ev: CompilerLogEvent = [level, util.format(message, ...args)];
8
- process.send(ev);
9
- } else {
4
+ function log(level: CompilerLogLevel, message: string, ...args: unknown[]): void {
5
+ EventUtil.sendEvent('log', { level, message, args, time: Date.now(), scope: 'compiler-exec' });
6
+ if (!process.send) {
10
7
  // eslint-disable-next-line no-console
11
8
  console[level](message, ...args);
12
9
  }
13
10
  }
14
11
 
15
12
  export const Log = {
13
+ warn: log.bind(null, 'warn'),
16
14
  debug: log.bind(null, 'debug'),
17
- info: log.bind(null, 'info')
15
+ info: log.bind(null, 'info'),
16
+ error: log.bind(null, 'error'),
18
17
  };
package/src/state.ts CHANGED
@@ -4,8 +4,8 @@ import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex }
4
4
  import { TransformerManager } from '@travetto/transformer';
5
5
 
6
6
  import { CompilerUtil } from './util';
7
- import { TranspileUtil } from '../support/transpile';
8
7
  import { CompileStateEntry } from './types';
8
+ import { CommonUtil } from '../support/util';
9
9
 
10
10
  function folderMapper(root: string, prefix: string): { dir: string, translate: (val: string) => string } {
11
11
  let matched: string = '~~';
@@ -74,7 +74,7 @@ export class CompilerState implements ts.CompilerHost {
74
74
  this.#transformerManager = await TransformerManager.create(this.#manifestIndex);
75
75
 
76
76
  this.#compilerOptions = {
77
- ...await TranspileUtil.getCompilerOptions(this.#manifest),
77
+ ...await CommonUtil.getCompilerOptions(this.#manifest),
78
78
  rootDir: this.#rootDir,
79
79
  outDir: this.#outputPath
80
80
  };
@@ -123,7 +123,7 @@ export class CompilerState implements ts.CompilerHost {
123
123
  return this.#sourceToEntry.get(sourceFile);
124
124
  }
125
125
 
126
- registerInput(module: ManifestModule, moduleFile: string): string {
126
+ registerInput(module: ManifestModule, moduleFile: string): CompileStateEntry {
127
127
  const relativeInput = `${module.outputFolder}/${moduleFile}`;
128
128
  const sourceFile = path.resolve(this.#manifest.workspacePath, module.sourceFolder, moduleFile);
129
129
  const sourceFolder = path.dirname(sourceFile);
@@ -146,7 +146,7 @@ export class CompilerState implements ts.CompilerHost {
146
146
 
147
147
  this.#inputFiles.add(inputFile);
148
148
 
149
- return inputFile;
149
+ return entry;
150
150
  }
151
151
 
152
152
  removeInput(inputFile: string): void {
package/src/types.ts CHANGED
@@ -6,9 +6,3 @@ export type CompileEmitError = Error | readonly ts.Diagnostic[];
6
6
  export type CompileEmitter = (file: string, newProgram?: boolean) => Promise<CompileEmitError | undefined>;
7
7
  export type CompileEmitEvent = { file: string, i: number, total: number, err?: CompileEmitError };
8
8
  export type CompileStateEntry = { source: string, input: string, relativeInput: string, output?: string, module: ManifestModule };
9
-
10
- export type CompileWatcherHandler = {
11
- create: (inputFile: string) => void;
12
- update: (inputFile: string) => void;
13
- delete: (outputFile: string) => void;
14
- };