@travetto/compiler 3.0.2 → 3.1.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
@@ -1,6 +1,7 @@
1
1
  <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
- <!-- Please modify https://github.com/travetto/travetto/tree/main/module/compiler/DOC.ts and execute "npx trv doc" to rebuild -->
2
+ <!-- Please modify https://github.com/travetto/travetto/tree/main/module/compiler/DOC.tsx and execute "npx trv doc" to rebuild -->
3
3
  # Compiler
4
+
4
5
  ## The compiler infrastructure for the Travetto framework
5
6
 
6
7
  **Install: @travetto/compiler**
@@ -13,37 +14,52 @@ yarn add @travetto/compiler
13
14
  ```
14
15
 
15
16
  This module expands upon the [Typescript](https://typescriptlang.org) compiler, with the additional features:
16
-
17
17
  * Integration with the [Transformation](https://github.com/travetto/travetto/tree/main/module/transformer#readme "Functionality for AST transformations, with transformer registration, and general utils") module, allowing for rich, type-aware transformations
18
18
  * Automatic conversion to either [Ecmascript Module](https://nodejs.org/api/esm.html) or [CommonJS](https://nodejs.org/api/modules.html) based on the [Package JSON](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) `type` value
19
19
  * Removal of type only imports which can break [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
20
20
  * Automatic addition of `.js` extension to imports to also support [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
21
-
22
- Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the module provides the primary entry point into the development process.
21
+ Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the module provides the primary entry point into the development process.
23
22
 
24
23
  ## CLI
25
-
26
- 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 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.
27
25
 
28
26
  The CLI supports the following operations:
29
-
30
-
31
27
  * `clean` - Removes the output folder, and if `-a` is also passed, will also clean out the compiler folder
32
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.
33
29
  * `watch` - If nothing else is watching, will start the watch operation. Otherwise will return immediately.
34
- * `manifest` - Will produce a manifest. If no file is passed in the command line arguments, will output to stdout
30
+ * `manifest` - Will produce a manifest. If no file is passed in the command line arguments, will output to stdout.
35
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.
36
-
37
32
  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`.
38
33
 
39
34
  **Terminal: Sample trv output with debug logging**
40
35
  ```bash
41
36
  $ TRV_BUILD=debug trv build
42
37
 
43
- 2029-03-14T04:00:00.618Z [lock ] watch pid=000000 Started
44
- 2029-03-14T04:00:00.837Z [lock ] watch pid=000000 Already running, and has built
45
- 2029-03-14T04:00:01.510Z [lock ] watch pid=000000 Completed
46
- 2029-03-14T04:00:02.450Z [build ] Successfully built
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
47
63
  ```
48
64
 
49
65
  **Terminal: Sample trv output with default log level**
@@ -52,9 +68,7 @@ $ trv build
52
68
  ```
53
69
 
54
70
  ## Compilation Architecture
55
-
56
71
  The compiler will move through the following phases on a given compilation execution:
57
-
58
72
  * `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
59
73
  * `Lock Management` - Manages cross-process interaction to ensure single compiler
60
74
  * `Build Compiler` - Leverages [Typescript](https://typescriptlang.org) to build files needed to execute compiler
@@ -66,17 +80,12 @@ The compiler will move through the following phases on a given compilation execu
66
80
  * `Invoke Compiler` - Run [Typescript](https://typescriptlang.org) compiler with the aforementioned enhancements
67
81
 
68
82
  ### Bootstrapping
69
-
70
- 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.
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.
71
84
 
72
85
  ### Lock Management
73
-
74
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:
75
-
76
-
77
87
  * No Watch - Building
78
88
  * Watch - No Build
79
89
  * Watch - Building
80
90
  * Inactive / Stale
81
-
82
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.
package/bin/trv.js CHANGED
@@ -9,7 +9,7 @@ import { getManifestContext } from '@travetto/manifest/bin/context.js';
9
9
 
10
10
  const VALID_OPS = { watch: 'watch', build: 'build', clean: 'clean', manifest: 'manifest' };
11
11
 
12
- const COMPILER_FILES = [...['launcher', 'transpile', 'lock', 'log', 'lock-pinger'].map(x => `support/${x}.js`), 'package.json'];
12
+ const COMPILER_FILES = [...['launcher', 'transpile', 'lock', 'log', 'lock-pinger'].map(x => `support/${x}.ts`), 'package.json'];
13
13
 
14
14
  /**
15
15
  * @param {import('@travetto/manifest').ManifestContext} ctx
@@ -20,12 +20,12 @@ const $getLauncher = async (ctx) => {
20
20
  if (!(await fs.stat(tsconfigFile).catch(() => undefined))) {
21
21
  await fs.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }), 'utf8');
22
22
  }
23
- const compPkg = createRequire(path.resolve(ctx.workspacePath, 'node_modules')).resolve('@travetto/compiler/package.json');
23
+ const compMod = path.dirname(createRequire(path.resolve(ctx.workspacePath, 'node_modules')).resolve('@travetto/compiler/package.json'));
24
24
  const files = [];
25
25
 
26
26
  for (const file of COMPILER_FILES) {
27
- const target = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/compiler', file);
28
- const src = compPkg.replace('package.json', file.replace(/[.]js$/, '.ts'));
27
+ const target = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/compiler', file).replace(/[.]tsx?$/, '.js');
28
+ const src = path.resolve(compMod, file);
29
29
 
30
30
  const targetTime = await fs.stat(target).then(s => Math.max(s.mtimeMs, s.ctimeMs)).catch(() => 0);
31
31
  const srcTime = await fs.stat(src).then(s => Math.max(s.mtimeMs, s.ctimeMs));
@@ -35,7 +35,7 @@ const $getLauncher = async (ctx) => {
35
35
  const module = ctx.moduleType === 'module' ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS;
36
36
  await fs.mkdir(path.dirname(target), { recursive: true });
37
37
  const text = await fs.readFile(src, 'utf8');
38
- if (file.endsWith('.js')) {
38
+ if (/[.]tsx?$/.test(file)) {
39
39
  const content = ts.transpile(
40
40
  text,
41
41
  { target: ts.ScriptTarget.ES2020, module, esModuleInterop: true, allowSyntheticDefaultImports: true }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "3.0.2",
3
+ "version": "3.1.0-rc.0",
4
4
  "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
@@ -31,12 +31,12 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@parcel/watcher": "^2.1.0",
34
- "@travetto/manifest": "^3.0.2",
35
- "@travetto/terminal": "^3.0.2",
36
- "@travetto/transformer": "^3.0.2"
34
+ "@travetto/manifest": "^3.1.0-rc.0",
35
+ "@travetto/terminal": "^3.1.0-rc.0",
36
+ "@travetto/transformer": "^3.1.0-rc.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@travetto/cli": "^3.0.2"
39
+ "@travetto/cli": "^3.1.0-rc.0"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -3,7 +3,7 @@ import ts from 'typescript';
3
3
  import fs from 'fs/promises';
4
4
 
5
5
  import { GlobalTerminal, TerminalProgressEvent } from '@travetto/terminal';
6
- import { RootIndex } from '@travetto/manifest';
6
+ import { ManifestModuleUtil, RootIndex } from '@travetto/manifest';
7
7
 
8
8
  import { CompilerUtil } from './util';
9
9
  import { CompilerState } from './state';
@@ -22,7 +22,7 @@ export class Compiler {
22
22
  static async main(): Promise<void> {
23
23
  const [dirty, watch] = process.argv.slice(2);
24
24
  const state = await CompilerState.get(RootIndex);
25
- const dirtyFiles = (await fs.readFile(dirty, 'utf8')).split(/\n/).filter(x => !!x);
25
+ const dirtyFiles = ManifestModuleUtil.getFileType(dirty) === 'ts' ? [dirty] : (await fs.readFile(dirty, 'utf8')).split(/\n/).filter(x => !!x);
26
26
  await new Compiler(state, dirtyFiles, watch === 'true').run();
27
27
  process.exit(0);
28
28
  }
@@ -39,20 +39,6 @@ export class Compiler {
39
39
  this.#watch = watch;
40
40
  }
41
41
 
42
- /**
43
- * Watches local modules
44
- */
45
- #watchLocalModules(emit: CompileEmitter): Promise<() => Promise<void>> {
46
- return new CompilerWatcher(this.#state).watchFiles(async file => {
47
- const err = await emit(file, true);
48
- if (err) {
49
- Log.info('Compilation Error', CompilerUtil.buildTranspileError(file, err));
50
- } else {
51
- Log.info(`Compiled ${file.split('node_modules/')[1]}`);
52
- }
53
- return err;
54
- });
55
- }
56
42
 
57
43
  /**
58
44
  * Compile in a single pass, only emitting dirty files
@@ -119,7 +105,7 @@ export class Compiler {
119
105
  };
120
106
 
121
107
  if (this.#dirtyFiles.length) {
122
- await GlobalTerminal.trackProgress(this.emit(this.#dirtyFiles, emitter), resolveEmittedFile, { position: 'bottom' });
108
+ await GlobalTerminal.trackProgress(this.emit(this.#dirtyFiles, emitter), resolveEmittedFile, { position: 'bottom', minDelay: 50 });
123
109
  if (failed) {
124
110
  Log.debug('Compilation failed');
125
111
  process.exit(1);
@@ -136,13 +122,21 @@ export class Compiler {
136
122
 
137
123
  if (this.#watch) {
138
124
  Log.info('Watch is ready');
139
- await this.#watchLocalModules(emitter);
140
- const output = this.#state.resolveOutputFile('.');
141
- for await (const _ of fs.watch(output)) {
142
- if (!await fs.stat(output).catch(() => false)) {
143
- process.send?.('restart');
125
+ for await (const { file, action } of CompilerWatcher.watch(this.#state)) {
126
+ if (action !== 'delete') {
127
+ const err = await emitter(file, true);
128
+ if (err) {
129
+ Log.info('Compilation Error', CompilerUtil.buildTranspileError(file, err));
130
+ } else {
131
+ Log.info(`Compiled ${file.split('node_modules/')[1]}`);
132
+ }
133
+ } else {
134
+ Log.info(`Removed ${file.split('node_modules/')[1]}`);
144
135
  }
145
136
  }
137
+ if (!process.exitCode) {
138
+ process.send?.('restart');
139
+ }
146
140
  }
147
141
  }
148
142
  }
package/src/state.ts CHANGED
@@ -104,17 +104,18 @@ export class CompilerState implements ts.CompilerHost {
104
104
  return prog;
105
105
  }
106
106
 
107
- writeInputFile(program: ts.Program, inputFile: string): ts.EmitResult | undefined {
108
- if (inputFile.endsWith('.json')) {
109
- this.writeFile(this.#inputToEntry.get(inputFile)!.output!, this.readFile(inputFile)!, false);
110
- } else if (inputFile.endsWith('.js')) {
111
- this.writeFile(this.#inputToEntry.get(inputFile)!.output!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
112
- } else if (inputFile.endsWith('.ts')) {
113
- return program.emit(
114
- program.getSourceFile(inputFile)!,
115
- (...args) => this.writeFile(...args), undefined, false,
116
- this.#transformerManager.get()
117
- );
107
+ writeInputFile(program: ts.Program, inputFile: string): ts.EmitResult | undefined | void {
108
+ switch (ManifestModuleUtil.getFileType(inputFile)) {
109
+ case 'package-json':
110
+ return this.writeFile(this.#inputToEntry.get(inputFile)!.output!, this.readFile(inputFile)!, false);
111
+ case 'js':
112
+ return this.writeFile(this.#inputToEntry.get(inputFile)!.output!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
113
+ case 'ts':
114
+ return program.emit(
115
+ program.getSourceFile(inputFile)!,
116
+ (...args) => this.writeFile(...args), undefined, false,
117
+ this.#transformerManager.get()
118
+ );
118
119
  }
119
120
  }
120
121
 
@@ -131,7 +132,7 @@ export class CompilerState implements ts.CompilerHost {
131
132
  const fileType = ManifestModuleUtil.getFileType(moduleFile);
132
133
  const outputFile = fileType === 'typings' ?
133
134
  undefined :
134
- path.resolve(this.#outputPath, CompilerUtil.inputToOutput(relativeInput));
135
+ path.resolve(this.#outputPath, ManifestModuleUtil.sourceToOutputExt(relativeInput));
135
136
 
136
137
  const entry = { source: sourceFile, input: inputFile, output: outputFile, module, relativeInput };
137
138
 
package/src/util.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import ts from 'typescript';
2
2
 
3
- import { ManifestContext, ManifestModuleFileType, ManifestRoot, Package, path } from '@travetto/manifest';
3
+ import { ManifestContext, ManifestModuleFileType, ManifestModuleUtil, ManifestRoot, Package, path } from '@travetto/manifest';
4
4
 
5
5
  type OutputToSource = (outputFile: string) => ({ source: string } | undefined);
6
- export type FileWatchEvent = { type: 'create' | 'delete' | 'update', path: string };
7
6
 
8
7
  const nativeCwd = process.cwd();
9
8
 
@@ -17,15 +16,6 @@ export class CompilerUtil {
17
16
  */
18
17
  static validFile = (type: ManifestModuleFileType): boolean => type === 'ts' || type === 'package-json' || type === 'js';
19
18
 
20
- /**
21
- * Map input file to output format, generally converting ts extensions to js
22
- * @param file
23
- * @returns
24
- */
25
- static inputToOutput(file: string): string {
26
- return file.replace(/[.][tj]s$/, '.js');
27
- }
28
-
29
19
  /**
30
20
  * Determines if write callback data has sourcemap information
31
21
  * @param data
@@ -41,7 +31,7 @@ export class CompilerUtil {
41
31
  */
42
32
  static rewriteSourceMap(ctx: ManifestContext, text: string, outputToSource: OutputToSource): string {
43
33
  const data: { sourceRoot?: string, sources: string[] } = JSON.parse(text);
44
- const output = this.inputToOutput(path.resolve(ctx.workspacePath, ctx.outputFolder, data.sources[0]));
34
+ const output = ManifestModuleUtil.sourceToOutputExt(path.resolve(ctx.workspacePath, ctx.outputFolder, data.sources[0]));
45
35
  const { source: file } = outputToSource(output) ?? {};
46
36
 
47
37
  if (file) {
@@ -77,7 +67,7 @@ export class CompilerUtil {
77
67
  }
78
68
 
79
69
  /**
80
- * Rewrites the package.json to target .js files instead of .ts files, and pins versions
70
+ * Rewrites the package.json to target output file names, and pins versions
81
71
  * @param manifest
82
72
  * @param file
83
73
  * @param text
@@ -86,10 +76,10 @@ export class CompilerUtil {
86
76
  static rewritePackageJSON(manifest: ManifestRoot, text: string): string {
87
77
  const pkg: Package = JSON.parse(text);
88
78
  if (pkg.files) {
89
- pkg.files = pkg.files.map(x => this.inputToOutput(x));
79
+ pkg.files = pkg.files.map(x => ManifestModuleUtil.sourceToOutputExt(x));
90
80
  }
91
81
  if (pkg.main) {
92
- pkg.main = this.inputToOutput(pkg.main);
82
+ pkg.main = ManifestModuleUtil.sourceToOutputExt(pkg.main);
93
83
  }
94
84
  pkg.type = manifest.moduleType;
95
85
  for (const key of ['devDependencies', 'dependencies', 'peerDependencies'] as const) {
package/src/watch.ts CHANGED
@@ -1,21 +1,28 @@
1
1
  import { readFileSync } from 'fs';
2
- import fs from 'fs/promises';
3
2
 
4
3
  import {
5
4
  ManifestContext, ManifestModuleUtil, ManifestUtil, WatchEvent, ManifestModuleFolderType,
6
- ManifestModuleFileType, path, ManifestModule, watchFolders, WatchEventListener, watchFolderImmediate, WatchConfig
5
+ ManifestModuleFileType, path, ManifestModule, watchFolders, WatchFolder, RootIndex, WatchStream
7
6
  } from '@travetto/manifest';
8
7
  import { getManifestContext } from '@travetto/manifest/bin/context';
9
8
 
10
9
  import { CompilerState } from './state';
11
10
  import { CompilerUtil } from './util';
12
- import { CompileEmitter, CompileWatcherHandler } from './types';
13
11
 
14
12
  /**
15
13
  * Utils for watching
16
14
  */
17
15
  export class CompilerWatcher {
18
16
 
17
+ /**
18
+ * Watch state
19
+ * @param state
20
+ * @returns
21
+ */
22
+ static watch(state: CompilerState): AsyncIterable<WatchEvent> {
23
+ return new CompilerWatcher(state).watchChanges();
24
+ }
25
+
19
26
  #sourceHashes = new Map<string, number>();
20
27
  #manifestContexts = new Map<string, ManifestContext>();
21
28
  #dirtyFiles: { modFolder: string, mod: string, moduleFile?: string, folderKey?: ManifestModuleFolderType, type?: ManifestModuleFileType }[] = [];
@@ -62,15 +69,21 @@ export class CompilerWatcher {
62
69
  }
63
70
 
64
71
  /**
65
- * Get a watcher for a given compiler state
66
- * @param state
67
- * @param handler
68
- * @returns
69
- */
70
- #getWatcher(handler: CompileWatcherHandler): WatchEventListener {
72
+ * Get a watcher for a given compiler state
73
+ * @param state
74
+ * @param handler
75
+ * @returns
76
+ */
77
+ async * watchChanges(): AsyncIterable<WatchEvent> {
78
+ const stream = this.#watchFiles();
79
+
71
80
  const mods = this.#getModuleMap();
81
+ for await (const { file: sourceFile, action, folder } of stream) {
82
+
83
+ if (folder === '.trv_internal') {
84
+ break;
85
+ }
72
86
 
73
- return async ({ file: sourceFile, action }: WatchEvent, folder: string): Promise<void> => {
74
87
  const mod = mods[folder];
75
88
  const moduleFile = mod.sourceFolder ?
76
89
  (sourceFile.includes(mod.sourceFolder) ? sourceFile.split(`${mod.sourceFolder}/`)[1] : sourceFile) :
@@ -91,7 +104,7 @@ export class CompilerWatcher {
91
104
  const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
92
105
  const input = this.#state.registerInput(mod, moduleFile);
93
106
  this.#sourceHashes.set(sourceFile, hash);
94
- handler.create(input);
107
+ yield { action, file: input, folder };
95
108
  }
96
109
  break;
97
110
  }
@@ -103,7 +116,7 @@ export class CompilerWatcher {
103
116
  if (this.#sourceHashes.get(sourceFile) !== hash) {
104
117
  this.#state.resetInputSource(entry.input);
105
118
  this.#sourceHashes.set(sourceFile, hash);
106
- handler.update(entry.input);
119
+ yield { action, file: entry.input, folder };
107
120
  }
108
121
  }
109
122
  break;
@@ -114,46 +127,58 @@ export class CompilerWatcher {
114
127
  this.#state.removeInput(entry.input);
115
128
  if (entry.output) {
116
129
  this.#dirtyFiles.push({ mod: mod.name, modFolder: folder });
117
- handler.delete(entry.output);
130
+ yield { action, file: entry.output, folder };
118
131
  }
119
132
  }
120
133
  }
121
134
  }
122
- };
135
+ }
123
136
  }
124
137
 
125
138
  /**
126
139
  * Watch files based on root index
127
140
  */
128
- async watchFiles(emit: CompileEmitter): Promise<() => Promise<void>> {
129
- let watchRoot: (() => Promise<void>) | undefined = undefined;
130
-
141
+ #watchFiles(): WatchStream {
131
142
  const idx = this.#state.manifestIndex;
132
143
  const modules = [...idx.getModuleList('all')].map(x => idx.getModule(x)!);
133
- const remove = (outputFile: string): Promise<void> => fs.rm(outputFile, { force: true });
134
- const handler = this.#getWatcher({ create: emit, update: emit, delete: remove });
135
- const options: WatchConfig = {
136
- filter: ev => ev.file.endsWith('.ts') || ev.file.endsWith('.js') || ev.file.endsWith('package.json'),
137
- ignore: ['node_modules']
144
+ const options: Partial<WatchFolder> = {
145
+ filter: (ev: WatchEvent): boolean => {
146
+ const type = ManifestModuleUtil.getFileType(ev.file);
147
+ return type === 'ts' || type === 'typings' || type === 'js' || type === 'package-json';
148
+ },
149
+ ignore: ['node_modules', '**/.trv_*'],
138
150
  };
139
151
 
140
- const moduleFolders = modules
152
+ const moduleFolders: WatchFolder[] = modules
141
153
  .filter(x => !idx.manifest.monoRepo || x.sourcePath !== idx.manifest.workspacePath)
142
- .map(x => [x.sourcePath, x.sourcePath] as const);
154
+ .map(x => ({ src: x.sourcePath, target: x.sourcePath }));
143
155
 
144
156
  // Add monorepo folders
145
157
  if (idx.manifest.monoRepo) {
146
158
  const mono = modules.find(x => x.sourcePath === idx.manifest.workspacePath)!;
147
159
  for (const folder of Object.keys(mono.files)) {
148
160
  if (!folder.startsWith('$')) {
149
- moduleFolders.push([path.resolve(mono.sourcePath, folder), mono.sourcePath]);
161
+ moduleFolders.push({ src: path.resolve(mono.sourcePath, folder), target: mono.sourcePath });
150
162
  }
151
163
  }
152
- watchRoot = await watchFolderImmediate(mono.sourcePath, handler, options);
164
+ moduleFolders.push({ src: mono.sourcePath, target: mono.sourcePath, immediate: true });
153
165
  }
154
166
 
155
- const watchAll = await watchFolders(moduleFolders, handler, options);
167
+ // Watch output folders
168
+ const outputWatch = (root: string, sources: string[]): WatchFolder => {
169
+ const valid = new Set(sources.map(src => path.resolve(root, src)));
170
+ return {
171
+ src: root, target: '.trv_internal', immediate: true, includeHidden: true,
172
+ filter: ev => ev.action === 'delete' && valid.has(path.resolve(root, ev.file))
173
+ };
174
+ };
175
+ moduleFolders.push(
176
+ outputWatch(RootIndex.manifest.workspacePath, [
177
+ RootIndex.manifest.outputFolder,
178
+ RootIndex.manifest.compilerFolder
179
+ ])
180
+ );
156
181
 
157
- return () => Promise.all([watchRoot?.(), watchAll()]).then(() => { });
182
+ return watchFolders(moduleFolders, options);
158
183
  }
159
184
  }
@@ -1,6 +1,7 @@
1
1
  import path from 'path';
2
2
  import fs from 'fs/promises';
3
3
  import os from 'os';
4
+ import timers from 'timers/promises';
4
5
  import cp from 'child_process';
5
6
  import { createRequire } from 'module';
6
7
 
@@ -19,6 +20,27 @@ const RECENT_STAT = (stat: { ctimeMs: number, mtimeMs: number }): number => Math
19
20
  * Transpile utilities for launching
20
21
  */
21
22
  export class TranspileUtil {
23
+ /**
24
+ * Determine file type
25
+ */
26
+ static getFileType(file: string): 'ts' | 'js' | 'package-json' | 'typings' | undefined {
27
+ return file.endsWith('package.json') ? 'package-json' :
28
+ (file.endsWith('.js') ? 'js' :
29
+ (file.endsWith('.d.ts') ? 'typings' : (/[.]tsx?$/.test(file) ? 'ts' : undefined)));
30
+ }
31
+
32
+ /** Convert a file to a given ext */
33
+ static #sourceToExtension(inputFile: string, ext: string): string {
34
+ return inputFile.replace(/[.][tj]sx?$/, ext);
35
+ }
36
+
37
+ /**
38
+ * Get the output file name for a given input
39
+ */
40
+ static sourceToOutputExt(inputFile: string): string {
41
+ return this.#sourceToExtension(inputFile, '.js');
42
+ }
43
+
22
44
  /**
23
45
  * Write text file, and ensure folder exists
24
46
  */
@@ -45,8 +67,6 @@ export class TranspileUtil {
45
67
  OPT_CACHE[ctx.workspacePath] = {
46
68
  ...options,
47
69
  allowJs: true,
48
- sourceMap: false,
49
- inlineSourceMap: true,
50
70
  resolveJsonModule: true,
51
71
  sourceRoot: ctx.workspacePath,
52
72
  rootDir: ctx.workspacePath,
@@ -61,7 +81,8 @@ export class TranspileUtil {
61
81
  * Output a file, support for ts, js, and package.json
62
82
  */
63
83
  static async transpileFile(ctx: ManifestContext, inputFile: string, outputFile: string): Promise<void> {
64
- if (inputFile.endsWith('.ts') || inputFile.endsWith('.js')) {
84
+ const type = this.getFileType(inputFile);
85
+ if (type === 'js' || type === 'ts') {
65
86
  const compilerOut = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules');
66
87
 
67
88
  const text = (await fs.readFile(inputFile, 'utf8'))
@@ -69,12 +90,16 @@ export class TranspileUtil {
69
90
  .replace(/from '(@travetto\/(.*?))'/g, (_, i, s) => `from '${path.resolve(compilerOut, `${i}${s.includes('/') ? '.js' : '/__index__.js'}`)}'`);
70
91
 
71
92
  const ts = (await import('typescript')).default;
72
- const content = ts.transpile(text, await this.getCompilerOptions(ctx), inputFile);
93
+ const content = ts.transpile(text, {
94
+ ...await this.getCompilerOptions(ctx),
95
+ sourceMap: false,
96
+ inlineSourceMap: true,
97
+ }, inputFile);
73
98
  await this.writeTextFile(outputFile, content);
74
- } else if (inputFile.endsWith('package.json')) {
99
+ } else if (type === 'package-json') {
75
100
  const pkg: Package = JSON.parse(await fs.readFile(inputFile, 'utf8'));
76
- const main = pkg.main?.replace(/[.]ts$/, '.js');
77
- const files = pkg.files?.map(x => x.replace('.ts', '.js'));
101
+ const main = pkg.main ? this.sourceToOutputExt(pkg.main) : undefined;
102
+ const files = pkg.files?.map(x => this.sourceToOutputExt(x));
78
103
 
79
104
  const content = JSON.stringify({ ...pkg, main, type: ctx.moduleType, files }, null, 2);
80
105
  await this.writeTextFile(outputFile, content);
@@ -107,10 +132,12 @@ export class TranspileUtil {
107
132
 
108
133
  if (stat.isDirectory()) {
109
134
  folders.push(resolvedInput);
110
- } else if (file.endsWith('.d.ts')) {
111
- // Do nothing
112
- } else if (file.endsWith('.ts') || file.endsWith('.js')) {
113
- files.push(resolvedInput);
135
+ } else {
136
+ switch (this.getFileType(file)) {
137
+ case 'js':
138
+ case 'ts':
139
+ files.push(resolvedInput);
140
+ }
114
141
  }
115
142
  }
116
143
  }
@@ -118,7 +145,7 @@ export class TranspileUtil {
118
145
  const outputFolder = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', module);
119
146
  const out: ModFile[] = [];
120
147
  for (const input of files) {
121
- const output = input.replace(inputFolder, outputFolder).replace(/[.]ts$/, '.js');
148
+ const output = this.sourceToOutputExt(input.replace(inputFolder, outputFolder));
122
149
  const inputTs = await fs.stat(input).then(RECENT_STAT, () => 0);
123
150
  if (inputTs) {
124
151
  const outputTs = await fs.stat(output).then(RECENT_STAT, () => 0);
@@ -178,7 +205,7 @@ export class TranspileUtil {
178
205
  try {
179
206
  await this.writeTextFile(deltaFile, changedFiles.join('\n'));
180
207
 
181
- return await LogUtil.withLogger('compiler-exec', log => new Promise<CompileResult>((res, rej) => {
208
+ const result = await LogUtil.withLogger('compiler-exec', log => new Promise<CompileResult>((res, rej) => {
182
209
  proc = cp.spawn(process.argv0, [main, deltaFile, `${watch}`], {
183
210
  env: {
184
211
  ...process.env,
@@ -199,10 +226,19 @@ export class TranspileUtil {
199
226
  kill = (): void => { proc?.kill('SIGKILL'); };
200
227
  process.on('exit', kill);
201
228
  }));
229
+
230
+ if (result === 'restart') {
231
+ await timers.setTimeout(150 + 100 * Math.random());
232
+ }
233
+
234
+ return result;
202
235
  } finally {
203
236
  if (proc?.killed === false) { proc.kill('SIGKILL'); }
204
237
  if (kill) {
205
- process.removeListener('exit', kill);
238
+ process.off('exit', kill);
239
+ }
240
+ if (process.stdout.isTTY) {
241
+ process.stdout.write('\x1b[s\x1b[?25h\x1b[r\x1b[u');
206
242
  }
207
243
  await fs.rm(deltaFile, { force: true });
208
244
  }
package/tsconfig.trv.json CHANGED
@@ -6,6 +6,7 @@
6
6
  "lib": [
7
7
  "es2022"
8
8
  ],
9
+ "jsx": "react-jsx",
9
10
  "strict": true,
10
11
  "esModuleInterop": true,
11
12
  "strictPropertyInitialization": false,