@travetto/compiler 4.0.0-rc.0 → 4.0.0-rc.2

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,7 +21,7 @@ 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 compiler cli, [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js#L5) is the entry point for compilation-related operations. It 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, the compilation always targets 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#L4) is the entry point for compilation-related operations. It 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, the compilation always targets 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
26
  The compiler cli supports the following operations:
27
27
  * `start|watch` - Run the compiler in watch mode
@@ -39,35 +39,35 @@ In addition to the normal output, the compiler supports an environment variable
39
39
  $ TRV_BUILD=debug trvc build
40
40
 
41
41
  2029-03-14T04:00:00.618Z info [compiler-server] Starting server http://127.0.0.1:25539
42
- 2029-03-14T04:00:00.837Z debug [compiler ] Started, streaming logs
42
+ 2029-03-14T04:00:00.837Z debug [client.main ] Start Server
43
43
  2029-03-14T04:00:01.510Z debug [event-stream ] Started event stream
44
44
  2029-03-14T04:00:02.450Z debug [precompile ] Started
45
- 2029-03-14T04:00:02.762Z debug [compiler-server] Receive request { action: 'info', subAction: undefined }
46
- 2029-03-14T04:00:02.947Z debug [client.server ] Starting watch for events of type "log"
47
- 2029-03-14T04:00:03.093Z debug [compiler-server] Receive request { action: 'event', subAction: 'log' }
48
- 2029-03-14T04:00:04.003Z debug [precompile ] Skipped @travetto/manifest
49
- 2029-03-14T04:00:04.495Z debug [precompile ] Skipped @travetto/transformer
50
- 2029-03-14T04:00:05.066Z debug [precompile ] Skipped @travetto/compiler
51
- 2029-03-14T04:00:05.307Z debug [precompile ] Completed
52
- 2029-03-14T04:00:05.952Z debug [manifest ] Started
53
- 2029-03-14T04:00:06.859Z debug [manifest ] Completed
54
- 2029-03-14T04:00:07.720Z debug [transformers ] Started
55
- 2029-03-14T04:00:08.179Z debug [transformers ] Skipped @travetto/base
56
- 2029-03-14T04:00:08.588Z debug [transformers ] Skipped @travetto/cli
57
- 2029-03-14T04:00:09.493Z debug [transformers ] Skipped @travetto/manifest
58
- 2029-03-14T04:00:10.395Z debug [transformers ] Skipped @travetto/registry
59
- 2029-03-14T04:00:10.407Z debug [transformers ] Skipped @travetto/schema
60
- 2029-03-14T04:00:10.799Z debug [transformers ] Completed
61
- 2029-03-14T04:00:11.013Z debug [delta ] Started
62
- 2029-03-14T04:00:11.827Z debug [delta ] Completed
63
- 2029-03-14T04:00:11.894Z debug [manifest ] Started
64
- 2029-03-14T04:00:12.133Z debug [manifest ] Wrote manifest @travetto-doc/compiler
65
- 2029-03-14T04:00:13.123Z debug [manifest ] Completed
66
- 2029-03-14T04:00:14.014Z info [compiler-server] State changed: compile-end
67
- 2029-03-14T04:00:14.924Z debug [compiler-exec ] Skipped
68
- 2029-03-14T04:00:15.690Z debug [event-stream ] Finished event stream
69
- 2029-03-14T04:00:15.865Z info [compiler-server] Closing down server
70
- 2029-03-14T04:00:16.757Z debug [client.server ] Stopping watch for events of type "log"
45
+ 2029-03-14T04:00:02.762Z debug [precompile ] Skipped @travetto/manifest
46
+ 2029-03-14T04:00:02.947Z debug [precompile ] Skipped @travetto/transformer
47
+ 2029-03-14T04:00:03.093Z debug [precompile ] Skipped @travetto/compiler
48
+ 2029-03-14T04:00:04.003Z debug [precompile ] Completed
49
+ 2029-03-14T04:00:04.495Z debug [manifest ] Started
50
+ 2029-03-14T04:00:05.066Z debug [manifest ] Completed
51
+ 2029-03-14T04:00:05.307Z debug [transformers ] Started
52
+ 2029-03-14T04:00:05.952Z debug [transformers ] Skipped @travetto/base
53
+ 2029-03-14T04:00:06.859Z debug [transformers ] Skipped @travetto/cli
54
+ 2029-03-14T04:00:07.720Z debug [transformers ] Skipped @travetto/manifest
55
+ 2029-03-14T04:00:08.179Z debug [transformers ] Skipped @travetto/registry
56
+ 2029-03-14T04:00:08.588Z debug [transformers ] Skipped @travetto/schema
57
+ 2029-03-14T04:00:09.493Z debug [transformers ] Completed
58
+ 2029-03-14T04:00:10.395Z debug [delta ] Started
59
+ 2029-03-14T04:00:10.407Z debug [delta ] Completed
60
+ 2029-03-14T04:00:10.799Z debug [manifest ] Started
61
+ 2029-03-14T04:00:11.013Z debug [manifest ] Wrote manifest @travetto-doc/compiler
62
+ 2029-03-14T04:00:11.827Z debug [manifest ] Completed
63
+ 2029-03-14T04:00:11.894Z info [compiler-server] State changed: compile-end
64
+ 2029-03-14T04:00:12.133Z debug [compiler-exec ] Skipped
65
+ 2029-03-14T04:00:13.123Z debug [event-stream ] Finished event stream
66
+ 2029-03-14T04:00:14.014Z info [compiler-server] Closing down server
67
+ 2029-03-14T04:00:14.924Z debug [compiler-server] Server close event
68
+ 2029-03-14T04:00:15.690Z info [compiler-server] Closed down server
69
+ 2029-03-14T04:00:15.865Z debug [compiler-server] Finished processing events
70
+ 2029-03-14T04:00:16.757Z debug [client.main ] End Server
71
71
  ```
72
72
 
73
73
  **Terminal: Sample trv output with default log level**
@@ -88,4 +88,4 @@ The compiler will move through the following phases on a given compilation execu
88
88
  * `Invoke Compiler` - Run [Typescript](https://typescriptlang.org) compiler with the aforementioned enhancements
89
89
 
90
90
  ### Bootstrapping
91
- 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#L5) 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#L5) 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.
91
+ 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#L4) 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#L4) 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.
package/bin/common.js CHANGED
@@ -1,47 +1,60 @@
1
1
  // @ts-check
2
- import fs from 'node:fs/promises';
2
+ import { statSync, readFileSync, writeFileSync, mkdirSync, readdirSync, existsSync, rmSync } from 'node:fs';
3
3
  import path from 'node:path';
4
4
 
5
5
  import { getManifestContext } from '@travetto/manifest/bin/context.js';
6
6
 
7
7
  /** @typedef {import('@travetto/manifest').ManifestContext} Ctx */
8
- /** @typedef {(ctx: Ctx, content:string) => (string | Promise<string>)} Transform */
9
8
 
10
- const stat = (/** @type {string}*/ file) => fs.stat(file).then(s => Math.max(s.mtimeMs, s.ctimeMs)).catch(() => 0);
11
9
  const TS_EXT = /[.]tsx?$/;
12
10
 
13
- const /** @type {Transform} */ transpile = async (ctx, content, tsconfig = path.resolve(ctx.workspace.path, 'tsconfig.json')) => {
14
- await fs.stat(tsconfig).catch(() => fs.writeFile(tsconfig, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }), 'utf8'));
11
+ const getAge = (/** @type {{mtimeMs:number, ctimeMs:number}} */ st) => Math.max(st.mtimeMs, st.ctimeMs);
12
+
13
+ const getTarget = (/** @type {Ctx} */ ctx, file = '') => ({
14
+ dest: path.resolve(ctx.workspace.path, ctx.build.compilerFolder, 'node_modules', '@travetto/compiler', file).replace(TS_EXT, '.js'),
15
+ src: path.resolve(ctx.workspace.path, ctx.build.compilerModuleFolder, file),
16
+ async writeIfStale(/** @type {(text:string)=>(string|Promise<string>)}*/ transform) {
17
+ if (!existsSync(this.dest) || getAge(statSync(this.dest)) < getAge(statSync(this.src))) {
18
+ const text = readFileSync(this.src, 'utf8');
19
+ mkdirSync(path.dirname(this.dest), { recursive: true });
20
+ writeFileSync(this.dest, await transform(text), 'utf8');
21
+ }
22
+ }
23
+ });
24
+
25
+ const getTranspiler = async (/** @type {Ctx} */ ctx) => {
15
26
  const ts = (await import('typescript')).default;
16
27
  const module = ctx.workspace.type === 'module' ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS;
17
- return ts.transpile(content, { target: ts.ScriptTarget.ES2022, module, esModuleInterop: true, allowSyntheticDefaultImports: true });
28
+ return (content = '') => ts.transpile(content, { target: ts.ScriptTarget.ES2022, module, esModuleInterop: true, allowSyntheticDefaultImports: true });
18
29
  };
19
30
 
20
- const /** @type {Transform} */ rewritePackage = (ctx, content) =>
21
- JSON.stringify(Object.assign(JSON.parse(content), { type: ctx.workspace.type }), null, 2);
31
+ /** @returns {Promise<import('@travetto/compiler/support/entry.trvc')>} */
32
+ async function imp(f = '') { try { return require(f); } catch (err) { return import(f); } }
22
33
 
23
- async function outputIfChanged(/** @type {Ctx} */ ctx, /** @type {string} */ file, /** @type {Transform} */ transform) {
24
- const target = path.resolve(ctx.workspace.path, ctx.build.compilerFolder, 'node_modules', '@travetto/compiler', file).replace(TS_EXT, '.js');
25
- const src = path.resolve(ctx.workspace.path, ctx.build.compilerModuleFolder, file);
34
+ export async function getEntry() {
35
+ process.setSourceMapsEnabled(true); // Ensure source map during compilation/development
36
+ process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --enable-source-maps`; // Ensure it passes to children
26
37
 
27
- if (await stat(src) > await stat(target)) {
28
- await fs.mkdir(path.dirname(target), { recursive: true });
29
- const content = await fs.readFile(src, 'utf8');
30
- await fs.writeFile(target, await transform(ctx, content), 'utf8');
31
- }
32
- return target;
33
- }
38
+ const ctx = getManifestContext();
39
+ const target = getTarget.bind(null, ctx);
34
40
 
35
- export const getEntry = async () => {
36
- const ctx = await getManifestContext();
37
- const entry = await outputIfChanged(ctx, 'support/entry.trvc.ts', transpile);
38
- const run = (/** @type {import('@travetto/compiler/support/entry.trvc')} */ mod) => mod.main(ctx);
41
+ // Setup Tsconfig
42
+ const tsconfig = path.resolve(ctx.workspace.path, 'tsconfig.json');
43
+ existsSync(tsconfig) || writeFileSync(tsconfig, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }), 'utf8');
39
44
 
40
- await outputIfChanged(ctx, 'package.json', rewritePackage);
45
+ // Compile support folder
46
+ await target('package.json').writeIfStale(text => JSON.stringify(Object.assign(JSON.parse(text), { type: ctx.workspace.type }), null, 2));
41
47
 
42
- await fs.readdir(path.resolve(ctx.workspace.path, ctx.build.compilerModuleFolder, 'support'), { recursive: true }).then(files =>
43
- Promise.all(files.filter(x => TS_EXT.test(x)).map(f => outputIfChanged(ctx, `support/${f}`, transpile))));
48
+ let transpile;
49
+ for (const file of readdirSync(target('support').src, { recursive: true, encoding: 'utf8' })) {
50
+ if (TS_EXT.test(file)) { await target(`support/${file}`).writeIfStale(async (text) => (transpile ??= await getTranspiler(ctx))(text)); }
51
+ }
44
52
 
45
- try { return run(require(entry)); }
46
- catch { return import(entry).then(run); }
47
- };
53
+ // Load
54
+ try {
55
+ return await imp(target('support/entry.trvc.ts').dest).then(v => v.main(ctx));
56
+ } catch (err) {
57
+ rmSync(target('.').dest, { recursive: true, force: true });
58
+ throw err;
59
+ }
60
+ }
package/bin/trvc.js CHANGED
@@ -1,5 +1,4 @@
1
- #!/usr/bin/env -S node --disable-proto=delete --enable-source-maps
2
- process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --enable-source-maps --disable-proto=delete`;
1
+ #!/usr/bin/env node
3
2
 
4
3
  // @ts-check
5
4
  import { getEntry } from './common.js';
@@ -26,7 +25,7 @@ getEntry().then(async (ops) => {
26
25
  switch (op) {
27
26
  case undefined:
28
27
  case 'help': return console.log(`\n${help()}\n`);
29
- case 'restart': return ops.stop().then(() => ops.compile('watch'));
28
+ case 'restart': return ops.restart();
30
29
  case 'stop': return ops.stop();
31
30
  case 'info': return ops.info().then(v => console.log(JSON.stringify(v, null, 2)));
32
31
  case 'event': return ops.events(args[0], v => {
@@ -36,9 +35,9 @@ getEntry().then(async (ops) => {
36
35
  });
37
36
  case 'clean': return ops.clean();
38
37
  case 'manifest': return ops.manifest(args[0], flags.some(x => x === '--prod'));
39
- case 'start': return ops.compile('watch');
40
- case 'watch':
41
- case 'build': return ops.compile(op);
38
+ case 'start':
39
+ case 'watch': return ops.watch();
40
+ case 'build': return ops.build();
42
41
  default: console.error(`Unknown trvc operation: ${op}\n`); return console.error(help());
43
42
  }
44
43
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "4.0.0-rc.0",
3
+ "version": "4.0.0-rc.2",
4
4
  "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
@@ -30,13 +30,13 @@
30
30
  "directory": "module/compiler"
31
31
  },
32
32
  "dependencies": {
33
- "@parcel/watcher": "^2.3.0",
34
- "@travetto/manifest": "^4.0.0-rc.0",
35
- "@travetto/transformer": "^4.0.0-rc.0",
36
- "@types/node": "^20.10.3"
33
+ "@parcel/watcher": "^2.4.0",
34
+ "@travetto/manifest": "^4.0.0-rc.2",
35
+ "@travetto/transformer": "^4.0.0-rc.2",
36
+ "@types/node": "^20.11.16"
37
37
  },
38
38
  "peerDependencies": {
39
- "@travetto/cli": "^4.0.0-rc.0"
39
+ "@travetto/cli": "^4.0.0-rc.2"
40
40
  },
41
41
  "peerDependenciesMeta": {
42
42
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -33,18 +33,54 @@ export class Compiler {
33
33
  #watch?: boolean;
34
34
  #ctrl: AbortController;
35
35
  #signal: AbortSignal;
36
+ #shuttingDown = false;
36
37
 
37
38
  constructor(state: CompilerState, dirtyFiles: string[], watch?: boolean) {
38
39
  this.#state = state;
39
40
  this.#dirtyFiles = dirtyFiles[0] === '*' ?
40
41
  this.#state.getAllFiles() :
41
- dirtyFiles.map(f => this.#state.getBySource(f)!.input);
42
+ dirtyFiles.map(f => this.#state.getBySource(f)!.inputFile);
42
43
  this.#watch = watch;
43
44
 
44
45
  this.#ctrl = new AbortController();
45
46
  this.#signal = this.#ctrl.signal;
46
47
  setMaxListeners(1000, this.#signal);
47
- process.once('disconnect', () => this.#ctrl.abort()).once('SIGINT', () => this.#ctrl.abort());
48
+ process
49
+ .once('disconnect', () => this.#shutdown('manual'))
50
+ .on('message', ev => (ev === 'shutdown') && this.#shutdown('manual'));
51
+ }
52
+
53
+ #shutdown(mode: 'error' | 'manual' | 'complete' | 'reset', err?: Error): void {
54
+ if (this.#shuttingDown) {
55
+ return;
56
+ }
57
+
58
+ this.#shuttingDown = true;
59
+ switch (mode) {
60
+ case 'manual': {
61
+ Log.error('Shutting down manually');
62
+ process.exitCode = 2;
63
+ break;
64
+ }
65
+ case 'error': {
66
+ process.exitCode = 1;
67
+ if (err) {
68
+ Log.error('Shutting down due to failure', err.message);
69
+ }
70
+ break;
71
+ }
72
+ case 'reset': {
73
+ Log.info('Triggering reset due to change in core files');
74
+ EventUtil.sendEvent('state', { state: 'reset' });
75
+ process.exitCode = 0;
76
+ break;
77
+ }
78
+ }
79
+ // No longer listen to disconnect
80
+ process.removeAllListeners('disconnect');
81
+ process.removeAllListeners('message');
82
+ this.#ctrl.abort();
83
+ setTimeout(() => process.exit(), 1000).unref(); // Allow upto 1s to shutdown gracefully
48
84
  }
49
85
 
50
86
  /**
@@ -145,48 +181,43 @@ export class Compiler {
145
181
  Log.info('Watch is ready');
146
182
 
147
183
  EventUtil.sendEvent('state', { state: 'watch-start' });
148
-
149
- for await (const ev of new CompilerWatcher(this.#state, this.#signal).watchChanges()) {
150
- if (ev.action === 'reset') {
151
- Log.info(`Triggering reset due to change in ${ev.file}`);
152
- EventUtil.sendEvent('state', { state: 'reset' });
153
- this.#ctrl.abort();
154
- return;
155
- }
156
- const { action, entry } = ev;
157
- if (action !== 'delete') {
158
- const err = await emitter(entry.input, true);
159
- if (err) {
160
- Log.info('Compilation Error', CompilerUtil.buildTranspileError(entry.input, err));
184
+ try {
185
+ for await (const ev of new CompilerWatcher(this.#state, this.#signal).watchChanges()) {
186
+ if (ev.action !== 'delete') {
187
+ const err = await emitter(ev.entry.inputFile, true);
188
+ if (err) {
189
+ Log.info('Compilation Error', CompilerUtil.buildTranspileError(ev.entry.inputFile, err));
190
+ } else {
191
+ Log.info(`Compiled ${ev.entry.sourceFile} on ${ev.action}`);
192
+ }
161
193
  } else {
162
- Log.info(`Compiled ${entry.source}`);
194
+ if (ev.entry.outputFile) {
195
+ // Remove output
196
+ Log.info(`Removed ${ev.entry.sourceFile}, ${ev.entry.outputFile}`);
197
+ await fs.rm(ev.entry.outputFile, { force: true }); // Ensure output is deleted
198
+ }
163
199
  }
164
- } else {
165
- // Remove output
166
- Log.info(`Removed ${entry.source}, ${entry.output}`);
167
- await fs.rm(entry.output!, { force: true }); // Ensure output is deleted
200
+
201
+ // Send change events
202
+ EventUtil.sendEvent('change', {
203
+ action: ev.action,
204
+ time: Date.now(),
205
+ file: ev.file,
206
+ output: ev.entry.outputFile!,
207
+ module: ev.entry.module.name
208
+ });
168
209
  }
210
+ EventUtil.sendEvent('state', { state: 'watch-end' });
169
211
 
170
- // Send change events
171
- EventUtil.sendEvent('change', {
172
- action: ev.action,
173
- time: Date.now(),
174
- file: ev.file,
175
- folder: ev.folder,
176
- output: ev.entry.output!,
177
- module: ev.entry.module.name
178
- });
212
+ } catch (err) {
213
+ if (err instanceof Error) {
214
+ this.#shutdown(err.message === 'RESET' ? 'reset' : 'error', err);
215
+ }
179
216
  }
180
-
181
- EventUtil.sendEvent('state', { state: 'watch-end' });
182
217
  }
183
218
 
184
- // No longer listen to disconnect
185
- process.removeAllListeners('disconnect');
219
+ Log.debug('Compiler process shutdown');
186
220
 
187
- if (this.#ctrl.signal.aborted) {
188
- process.exitCode = 2;
189
- Log.error('Shutting down manually');
190
- }
221
+ this.#shutdown('complete');
191
222
  }
192
223
  }
package/src/event.ts CHANGED
@@ -2,6 +2,6 @@ import type { CompilerEvent, CompilerEventType } from '../support/types';
2
2
 
3
3
  export class EventUtil {
4
4
  static sendEvent<K extends CompilerEventType, T extends CompilerEvent & { type: K }>(type: K, payload: T['payload']): void {
5
- process.connected && process.send!({ type, payload });
5
+ process.connected && process.send!({ type, payload }, undefined, { swallowErrors: true });
6
6
  }
7
7
  }
package/src/state.ts CHANGED
@@ -3,9 +3,10 @@ import ts from 'typescript';
3
3
  import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex } from '@travetto/manifest';
4
4
  import { TransformerManager } from '@travetto/transformer';
5
5
 
6
+ import { CommonUtil } from '../support/util';
7
+
6
8
  import { CompilerUtil } from './util';
7
9
  import { CompileStateEntry } from './types';
8
- import { CommonUtil } from '../support/util';
9
10
 
10
11
  function folderMapper(root: string, prefix: string): { dir: string, translate: (val: string) => string } {
11
12
  let matched: string = '~~';
@@ -26,7 +27,7 @@ export class CompilerState implements ts.CompilerHost {
26
27
  private constructor() { }
27
28
 
28
29
  #rootDir: string;
29
- #inputPathToSource: (file: string) => string;
30
+ #inputPathToSourcePath: (file: string) => string;
30
31
  #outputPath: string;
31
32
  #inputFiles = new Set<string>();
32
33
  #inputDirectoryToSource = new Map<string, string>();
@@ -36,6 +37,7 @@ export class CompilerState implements ts.CompilerHost {
36
37
 
37
38
  #sourceContents = new Map<string, string | undefined>();
38
39
  #sourceFileObjects = new Map<string, ts.SourceFile>();
40
+ #sourceHashes = new Map<string, number>();
39
41
 
40
42
  #manifestIndex: ManifestIndex;
41
43
  #manifest: ManifestRoot;
@@ -43,12 +45,16 @@ export class CompilerState implements ts.CompilerHost {
43
45
  #transformerManager: TransformerManager;
44
46
  #compilerOptions: ts.CompilerOptions;
45
47
 
48
+ #readFile(inputFile: string): string | undefined {
49
+ return ts.sys.readFile(this.#inputToEntry.get(inputFile)?.sourceFile ?? this.#inputPathToSourcePath(inputFile));
50
+ }
51
+
46
52
  async init(idx: ManifestIndex): Promise<this> {
47
53
  this.#manifestIndex = idx;
48
54
  this.#manifest = idx.manifest;
49
55
  const mapper = folderMapper(this.#manifest.workspace.path, '##');
50
56
  this.#rootDir = mapper.dir;
51
- this.#inputPathToSource = mapper.translate;
57
+ this.#inputPathToSourcePath = mapper.translate;
52
58
 
53
59
  this.#outputPath = path.resolve(this.#manifest.workspace.path, this.#manifest.build.outputFolder);
54
60
  this.#modules = Object.values(this.#manifest.modules);
@@ -95,7 +101,7 @@ export class CompilerState implements ts.CompilerHost {
95
101
  }
96
102
 
97
103
  getArbitraryInputFile(): string {
98
- return this.getBySource(this.#manifestIndex.getModule('@travetto/manifest')!.files.src[0].sourceFile)!.input;
104
+ return this.getBySource(this.#manifestIndex.getModule('@travetto/manifest')!.files.src[0].sourceFile)!.inputFile;
99
105
  }
100
106
 
101
107
  createProgram(oldProgram?: ts.Program): ts.Program {
@@ -107,9 +113,9 @@ export class CompilerState implements ts.CompilerHost {
107
113
  writeInputFile(program: ts.Program, inputFile: string): ts.EmitResult | undefined | void {
108
114
  switch (ManifestModuleUtil.getFileType(inputFile)) {
109
115
  case 'package-json':
110
- return this.writeFile(this.#inputToEntry.get(inputFile)!.output!, this.readFile(inputFile)!, false);
116
+ return this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, this.readFile(inputFile)!, false);
111
117
  case 'js':
112
- return this.writeFile(this.#inputToEntry.get(inputFile)!.output!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
118
+ return this.writeFile(this.#inputToEntry.get(inputFile)!.outputFile!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
113
119
  case 'ts':
114
120
  return program.emit(
115
121
  program.getSourceFile(inputFile)!,
@@ -134,7 +140,7 @@ export class CompilerState implements ts.CompilerHost {
134
140
  undefined :
135
141
  path.resolve(this.#outputPath, ManifestModuleUtil.sourceToOutputExt(relativeInput));
136
142
 
137
- const entry = { source: sourceFile, input: inputFile, output: outputFile, module, relativeInput };
143
+ const entry = { sourceFile, inputFile, outputFile, module };
138
144
 
139
145
  this.#inputToEntry.set(inputFile, entry);
140
146
  this.#sourceToEntry.set(sourceFile, entry);
@@ -145,23 +151,37 @@ export class CompilerState implements ts.CompilerHost {
145
151
  }
146
152
 
147
153
  this.#inputFiles.add(inputFile);
148
-
154
+ this.#sourceHashes.set(sourceFile, -1); // Unknown
149
155
  return entry;
150
156
  }
151
157
 
152
- removeInput(inputFile: string): void {
153
- const { output, source } = this.#inputToEntry.get(inputFile)!;
154
- if (output) {
155
- this.#outputToEntry.delete(output);
158
+ checkIfSourceChanged(inputFile: string): boolean {
159
+ const contents = this.#readFile(inputFile);
160
+ const prevHash = this.#sourceHashes.get(inputFile);
161
+ if (!contents || (contents.length === 0 && prevHash)) {
162
+ return false; // Ignore empty file
156
163
  }
157
- this.#sourceToEntry.delete(source);
158
- this.#inputToEntry.delete(inputFile);
159
- this.#inputFiles.delete(inputFile);
164
+ const currentHash = CompilerUtil.naiveHash(contents);
165
+ const changed = prevHash !== currentHash;
166
+ if (changed) {
167
+ this.#sourceHashes.set(inputFile, currentHash);
168
+ this.#sourceContents.set(inputFile, contents);
169
+ this.#sourceFileObjects.delete(inputFile);
170
+ }
171
+ return changed;
160
172
  }
161
173
 
162
- resetInputSource(inputFile: string): void {
174
+ removeInput(inputFile: string): void {
175
+ const { outputFile, sourceFile } = this.#inputToEntry.get(inputFile)!;
176
+ if (outputFile) {
177
+ this.#outputToEntry.delete(outputFile);
178
+ }
163
179
  this.#sourceFileObjects.delete(inputFile);
164
180
  this.#sourceContents.delete(inputFile);
181
+ this.#sourceHashes.delete(inputFile);
182
+ this.#sourceToEntry.delete(sourceFile);
183
+ this.#inputToEntry.delete(inputFile);
184
+ this.#inputFiles.delete(inputFile);
165
185
  }
166
186
 
167
187
  getAllFiles(): string[] {
@@ -177,11 +197,11 @@ export class CompilerState implements ts.CompilerHost {
177
197
  getDefaultLibLocation(): string { return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions)); }
178
198
 
179
199
  fileExists(inputFile: string): boolean {
180
- return this.#inputToEntry.has(inputFile) || ts.sys.fileExists(this.#inputPathToSource(inputFile));
200
+ return this.#inputToEntry.has(inputFile) || ts.sys.fileExists(this.#inputPathToSourcePath(inputFile));
181
201
  }
182
202
 
183
203
  directoryExists(inputDir: string): boolean {
184
- return this.#inputDirectoryToSource.has(inputDir) || ts.sys.directoryExists(this.#inputPathToSource(inputDir));
204
+ return this.#inputDirectoryToSource.has(inputDir) || ts.sys.directoryExists(this.#inputPathToSourcePath(inputDir));
185
205
  }
186
206
 
187
207
  writeFile(
@@ -203,9 +223,7 @@ export class CompilerState implements ts.CompilerHost {
203
223
  }
204
224
 
205
225
  readFile(inputFile: string): string | undefined {
206
- const res = this.#sourceContents.get(inputFile) ?? ts.sys.readFile(
207
- this.#inputToEntry.get(inputFile)?.source ?? this.#inputPathToSource(inputFile)
208
- );
226
+ const res = this.#sourceContents.get(inputFile) ?? this.#readFile(inputFile);
209
227
  this.#sourceContents.set(inputFile, res);
210
228
  return res;
211
229
  }
package/src/types.ts CHANGED
@@ -5,4 +5,4 @@ import type { ManifestModule } from '@travetto/manifest';
5
5
  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
- export type CompileStateEntry = { source: string, input: string, relativeInput: string, output?: string, module: ManifestModule };
8
+ export type CompileStateEntry = { sourceFile: string, inputFile: string, outputFile?: string, module: ManifestModule };
package/src/util.ts CHANGED
@@ -2,7 +2,7 @@ import ts from 'typescript';
2
2
 
3
3
  import { ManifestContext, ManifestModuleFileType, ManifestModuleUtil, ManifestRoot, Package, path } from '@travetto/manifest';
4
4
 
5
- type OutputToSource = (outputFile: string) => ({ source: string } | undefined);
5
+ type OutputToSource = (outputFile: string) => ({ sourceFile: string } | undefined);
6
6
 
7
7
  const nativeCwd = process.cwd();
8
8
 
@@ -32,11 +32,11 @@ export class CompilerUtil {
32
32
  static rewriteSourceMap(ctx: ManifestContext, text: string, outputToSource: OutputToSource): string {
33
33
  const data: { sourceRoot?: string, sources: string[] } = JSON.parse(text);
34
34
  const output = ManifestModuleUtil.sourceToOutputExt(path.resolve(ctx.workspace.path, ctx.build.outputFolder, data.sources[0]));
35
- const { source: file } = outputToSource(output) ?? {};
35
+ const { sourceFile } = outputToSource(output) ?? {};
36
36
 
37
- if (file) {
37
+ if (sourceFile) {
38
38
  delete data.sourceRoot;
39
- data.sources = [file];
39
+ data.sources = [sourceFile];
40
40
  text = JSON.stringify(data);
41
41
  }
42
42
  return text;