@travetto/compiler 8.0.0-alpha.1 → 8.0.0-alpha.11

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
@@ -25,7 +25,7 @@ Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the
25
25
  The compiler cli, [trvc](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trvc.js) 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.
26
26
 
27
27
  The compiler cli supports the following operations:
28
- * `start|watch` - Run the compiler in watch mode
28
+ * `start` - Run the compiler in watch mode
29
29
  * `stop` - Stop the compiler if running
30
30
  * `restart` - Restart the compiler in watch mode
31
31
  * `build` - Ensure the project is built and upto date
@@ -48,7 +48,7 @@ $ TRV_BUILD=debug trvc build
48
48
  2029-03-14T04:00:02.450Z info [compiler-exec ] Launching compiler
49
49
  2029-03-14T04:00:02.762Z debug [server ] Compilation started
50
50
  2029-03-14T04:00:02.947Z info [server ] State changed: init
51
- 2029-03-14T04:00:03.093Z debug [server ] Compiler loaded
51
+ 2029-03-14T04:00:03.093Z debug [server ] Compiler loaded: 0 files changed
52
52
  2029-03-14T04:00:04.003Z info [server ] State changed: compile-start
53
53
  2029-03-14T04:00:04.495Z info [server ] State changed: compile-end
54
54
  2029-03-14T04:00:05.066Z debug [server ] Compiler process shutdown
package/bin/hook.js CHANGED
@@ -1,26 +1,17 @@
1
+ // @ts-check
1
2
  import module from 'node:module';
2
3
  import { readFileSync } from 'node:fs';
3
4
  import { fileURLToPath } from 'node:url';
4
5
 
5
- import '@travetto/runtime/support/polyfill.js';
6
-
7
- process.setSourceMapsEnabled(true); // Ensure source map during compilation/development
8
- process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --enable-source-maps`; // Ensure it passes to children
9
- const ogEmitWarning = process.emitWarning;
10
- Error.stackTraceLimit = 50;
6
+ globalThis.devProcessWarningExclusions?.push((message) => message.startsWith('stripTypeScriptTypes'));
11
7
 
12
8
  module.registerHooks({
13
9
  load: (url, context, nextLoad) => {
14
10
  if (/[.]tsx?$/.test(url)) {
15
- try {
16
- process.emitWarning = () => { }; // Suppress ts-node experimental warnings
17
- const source = readFileSync(fileURLToPath(url), 'utf8');
18
- return { format: 'module', source: module.stripTypeScriptTypes(source), shortCircuit: true };
19
- } finally {
20
- process.emitWarning = ogEmitWarning;
21
- }
11
+ const source = readFileSync(fileURLToPath(url), 'utf8');
12
+ return { format: 'module', source: module.stripTypeScriptTypes(source), shortCircuit: true };
22
13
  } else {
23
14
  return nextLoad(url, context);
24
15
  }
25
16
  }
26
- });
17
+ });
@@ -1,4 +1,5 @@
1
1
  // @ts-check
2
+ import '@travetto/runtime/support/patch.js';
2
3
  import './hook.js';
3
4
  const { Compiler } = await import('../src/compiler.ts');
4
5
  await Compiler.main();
package/bin/trvc.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  // @ts-check
3
+ import '@travetto/runtime/support/patch.js';
3
4
  import './hook.js';
4
5
  const { invoke } = await import('@travetto/compiler/support/invoke.ts');
5
- await invoke();
6
+ await invoke(...process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "8.0.0-alpha.1",
3
+ "version": "8.0.0-alpha.11",
4
4
  "type": "module",
5
5
  "description": "The compiler infrastructure for the Travetto framework",
6
6
  "keywords": [
@@ -31,11 +31,11 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@parcel/watcher": "^2.5.6",
34
- "@travetto/manifest": "^8.0.0-alpha.1",
35
- "@travetto/transformer": "^8.0.0-alpha.1"
34
+ "@travetto/manifest": "^8.0.0-alpha.4",
35
+ "@travetto/transformer": "^8.0.0-alpha.5"
36
36
  },
37
37
  "peerDependencies": {
38
- "@travetto/cli": "^8.0.0-alpha.1"
38
+ "@travetto/cli": "^8.0.0-alpha.15"
39
39
  },
40
40
  "peerDependenciesMeta": {
41
41
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -3,10 +3,9 @@ import { setMaxListeners } from 'node:events';
3
3
 
4
4
  import { getManifestContext, ManifestDeltaUtil, ManifestIndex, ManifestUtil, type DeltaEvent } from '@travetto/manifest';
5
5
 
6
- import { CompilerUtil } from './util.ts';
7
6
  import { CompilerState } from './state.ts';
8
7
  import { CompilerWatcher } from './watch.ts';
9
- import { type CompileEmitEvent, type CompileEmitter, CompilerReset } from './types.ts';
8
+ import { type CompileEmitEvent, CompilerReset } from './types.ts';
10
9
  import { EventUtil } from './event.ts';
11
10
 
12
11
  import { IpcLogger } from './log.ts';
@@ -32,8 +31,8 @@ export class Compiler {
32
31
 
33
32
  #state: CompilerState;
34
33
  #watch?: boolean;
35
- #controller: AbortController;
36
- #signal: AbortSignal;
34
+ #shutdownController: AbortController;
35
+ #shutdownSignal: AbortSignal;
37
36
  #shuttingDown = false;
38
37
  #deltaEvents: DeltaEvent[];
39
38
 
@@ -42,15 +41,15 @@ export class Compiler {
42
41
  this.#watch = watch;
43
42
  this.#deltaEvents = deltaEvents;
44
43
 
45
- this.#controller = new AbortController();
46
- this.#signal = this.#controller.signal;
47
- setMaxListeners(1000, this.#signal);
44
+ this.#shutdownController = new AbortController();
45
+ this.#shutdownSignal = this.#shutdownController.signal;
46
+ setMaxListeners(1000, this.#shutdownSignal);
48
47
  process
49
48
  .once('disconnect', () => this.#shutdown('manual'))
50
49
  .on('message', event => (event === 'shutdown') && this.#shutdown('manual'));
51
50
  }
52
51
 
53
- #shutdown(mode: 'error' | 'manual' | 'complete' | 'reset', error?: Error): void {
52
+ #shutdown(mode: 'error' | 'manual' | 'complete' | 'reset', errorMessage?: string): void {
54
53
  if (this.#shuttingDown) {
55
54
  return;
56
55
  }
@@ -64,14 +63,13 @@ export class Compiler {
64
63
  }
65
64
  case 'error': {
66
65
  process.exitCode = 1;
67
- if (error) {
68
- EventUtil.sendEvent('log', { level: 'error', message: error.toString(), time: Date.now() });
69
- log.error('Shutting down due to failure', error.stack);
66
+ if (errorMessage) {
67
+ log.error('Shutting down due to failure', errorMessage);
70
68
  }
71
69
  break;
72
70
  }
73
71
  case 'reset': {
74
- log.info('Reset due to', error?.message);
72
+ log.info('Reset due to', errorMessage);
75
73
  EventUtil.sendEvent('state', { state: 'reset' });
76
74
  process.exitCode = 0;
77
75
  break;
@@ -80,7 +78,7 @@ export class Compiler {
80
78
  // No longer listen to disconnect
81
79
  process.removeAllListeners('disconnect');
82
80
  process.removeAllListeners('message');
83
- this.#controller.abort();
81
+ this.#shutdownController.abort();
84
82
  CommonUtil.nonBlockingTimeout(1000).then(() => process.exit()); // Allow upto 1s to shutdown gracefully
85
83
  }
86
84
 
@@ -110,35 +108,26 @@ export class Compiler {
110
108
  });
111
109
  }
112
110
 
113
- /**
114
- * Compile in a single pass, only emitting dirty files
115
- */
116
- getCompiler(): CompileEmitter {
117
- return (sourceFile: string, needsNewProgram?: boolean) => this.#state.compileSourceFile(sourceFile, needsNewProgram);
118
- }
119
-
120
111
  /**
121
112
  * Emit all files as a stream
122
113
  */
123
- async * emit(files: string[], emitter: CompileEmitter): AsyncIterable<CompileEmitEvent> {
114
+ async * emit(files: string[]): AsyncIterable<CompileEmitEvent> {
124
115
  let i = 0;
125
116
  let lastSent = Date.now();
126
117
 
127
- await emitter(files[0]); // Prime
128
-
129
118
  for (const file of files) {
130
119
  const start = Date.now();
131
- const error = await emitter(file);
120
+ const errors = await this.#state.compileSourceFile(file);
132
121
  const duration = Date.now() - start;
133
122
  const nodeModSeparator = 'node_modules/';
134
123
  const nodeModIdx = file.lastIndexOf(nodeModSeparator);
135
124
  const imp = nodeModIdx >= 0 ? file.substring(nodeModIdx + nodeModSeparator.length) : file;
136
- yield { file: imp, i: i += 1, error, total: files.length, duration };
125
+ yield { file: imp, i: i += 1, errors, total: files.length, duration };
137
126
  if ((Date.now() - lastSent) > 50) { // Limit to 1 every 50ms
138
127
  lastSent = Date.now();
139
128
  EventUtil.sendEvent('progress', { total: files.length, idx: i, message: imp, operation: 'compile' });
140
129
  }
141
- if (this.#signal.aborted) {
130
+ if (this.#shutdownSignal.aborted) {
142
131
  break;
143
132
  }
144
133
  }
@@ -157,10 +146,9 @@ export class Compiler {
157
146
 
158
147
  EventUtil.sendEvent('state', { state: 'init', extra: { processId: process.pid } });
159
148
 
160
- const emitter = this.getCompiler();
161
- let failure: Error | undefined;
149
+ const failures = new Map<string, number>();
162
150
 
163
- log.debug('Compiler loaded');
151
+ log.debug(`Compiler loaded: ${this.#deltaEvents.length} files changed`);
164
152
 
165
153
  EventUtil.sendEvent('state', { state: 'compile-start' });
166
154
 
@@ -168,38 +156,42 @@ export class Compiler {
168
156
  const isCompilerChanged = this.#deltaEvents.some(event => this.#state.isCompilerFile(event.sourceFile));
169
157
  const changedFiles = (isCompilerChanged ? this.#state.getAllFiles() : this.#deltaEvents.map(event => event.sourceFile));
170
158
 
171
- if (this.#watch || changedFiles.length) {
172
- await ManifestUtil.writeManifest(this.#state.manifestIndex.manifest);
173
- await this.#state.initializeTypescript();
174
- }
175
-
176
159
  if (changedFiles.length) {
177
- for await (const event of this.emit(changedFiles, emitter)) {
178
- if (event.error) {
179
- const compileError = CompilerUtil.buildTranspileError(event.file, event.error);
180
- failure ??= compileError;
181
- EventUtil.sendEvent('log', { level: 'error', message: compileError.toString(), time: Date.now() });
160
+ for await (const event of this.emit(changedFiles)) {
161
+ if (event.errors?.length) {
162
+ failures.set(event.file, event.errors.length);
163
+ for (const error of event.errors) {
164
+ log.error(`ERROR ${event.file}:${error}`);
165
+ }
166
+ // Touch file to ensure recompilation later
167
+ if (await fs.stat(event.file, { throwIfNoEntry: false })) {
168
+ await fs.utimes(event.file, new Date(), new Date());
169
+ }
182
170
  }
183
171
  metrics.push(event);
184
172
  }
185
- if (this.#signal.aborted) {
173
+ if (this.#shutdownSignal.aborted) {
186
174
  log.debug('Compilation aborted');
187
- } else if (failure) {
188
- log.debug('Compilation failed');
189
- return this.#shutdown('error', failure);
175
+ } else if (failures.size) {
176
+ const sortedFailures = [...failures.entries()].sort((a, b) => a[0].localeCompare(b[0]));
177
+ log.error('Compilation failed',
178
+ ['', sortedFailures.flatMap(([file, count]) => `- ${file}: ${count} errors found`)]
179
+ .flat(3).join('\n')
180
+ );
190
181
  } else {
191
182
  log.debug('Compilation succeeded');
192
183
  }
193
184
 
194
- // Rebuild manifests if dirty
185
+ // Rebuild manifests
195
186
  const manifest = await ManifestUtil.buildManifest(this.#state.manifestIndex.manifest);
196
187
  await ManifestUtil.writeManifest(manifest);
197
188
  await ManifestUtil.writeDependentManifests(manifest);
189
+
190
+ if (!this.#watch && failures.size) {
191
+ return this.#shutdown('error');
192
+ }
193
+
198
194
  this.#state.manifestIndex.reinitForModule(this.#state.manifest.main.name); // Reload
199
- } else if (this.#watch) {
200
- // Prime compiler before complete
201
- const resolved = this.#state.getArbitraryInputFile();
202
- await emitter(resolved, true);
203
195
  }
204
196
 
205
197
  EventUtil.sendEvent('state', { state: 'compile-end' });
@@ -208,16 +200,22 @@ export class Compiler {
208
200
  this.logStatistics(metrics);
209
201
  }
210
202
 
211
- if (this.#watch && !this.#signal.aborted) {
203
+ if (this.#watch && !this.#shutdownSignal.aborted) {
204
+ const resolved = this.#state.getArbitraryInputFile();
205
+ await this.#state.compileSourceFile(resolved);
206
+
212
207
  log.info('Watch is ready');
213
208
 
214
209
  EventUtil.sendEvent('state', { state: 'watch-start' });
215
210
  try {
216
- for await (const event of new CompilerWatcher(this.#state, this.#signal)) {
211
+ for await (const event of new CompilerWatcher(this.#state, this.#shutdownSignal)) {
217
212
  if (event.action !== 'delete') {
218
- const error = await emitter(event.entry.sourceFile, true);
219
- if (error) {
220
- log.info('Compilation Error', CompilerUtil.buildTranspileError(event.entry.sourceFile, error));
213
+ const errors = await this.#state.compileSourceFile(event.entry.sourceFile, true);
214
+ if (errors?.length) {
215
+ log.error('Compilation failed', `${event.entry.sourceFile}: ${errors.length} errors found`);
216
+ for (const error of errors) {
217
+ log.error(`ERROR ${event.file}:${error}`);
218
+ }
221
219
  } else {
222
220
  log.info(`Compiled ${event.entry.sourceFile} on ${event.action}`);
223
221
  }
@@ -242,7 +240,7 @@ export class Compiler {
242
240
  EventUtil.sendEvent('state', { state: 'watch-end' });
243
241
  } catch (error) {
244
242
  if (error instanceof Error) {
245
- this.#shutdown(error instanceof CompilerReset ? 'reset' : 'error', error);
243
+ this.#shutdown(error instanceof CompilerReset ? 'reset' : 'error', error.message);
246
244
  }
247
245
  }
248
246
  }
@@ -251,4 +249,4 @@ export class Compiler {
251
249
 
252
250
  this.#shutdown('complete');
253
251
  }
254
- }
252
+ }
@@ -48,8 +48,9 @@ export class CompilerManager {
48
48
 
49
49
  yield* queue;
50
50
 
51
- if (subProcess.exitCode !== 0) {
51
+ if (subProcess.exitCode !== 0 && subProcess.exitCode) {
52
52
  log.error(`Terminated during compilation, code=${subProcess.exitCode}, killed=${subProcess.killed}`);
53
+ process.exitCode = subProcess.exitCode;
53
54
  }
54
55
  process.off('SIGINT', kill);
55
56
 
@@ -76,7 +77,7 @@ export class CompilerManager {
76
77
  log.info('Server already running, waiting for initial compile to complete');
77
78
  const controller = new AbortController();
78
79
  Log.consumeProgressEvents(() => client.fetchEvents('progress', { until: event => !!event.complete, signal: controller.signal }));
79
- await client.waitForState(['compile-end', 'watch-start'], 'Successfully built');
80
+ await client.waitForState(['compile-end'], 'Successfully built');
80
81
  controller.abort();
81
82
  }
82
83
  }
@@ -27,12 +27,14 @@ export class CompilerServer {
27
27
  #client: CompilerClient;
28
28
  #url: string;
29
29
  #handle: Record<'compiler' | 'server', ProcessHandle>;
30
+ #suppressLogs: boolean;
30
31
 
31
- constructor(ctx: ManifestContext, watching: boolean) {
32
+ constructor(ctx: ManifestContext, watching: boolean, suppressLogs?: boolean) {
32
33
  this.#ctx = ctx;
33
34
  this.#client = new CompilerClient(ctx, Log.scoped('server.client'));
34
35
  this.#url = this.#client.url;
35
36
  this.#handle = { server: new ProcessHandle(ctx, 'server'), compiler: new ProcessHandle(ctx, 'compiler') };
37
+ this.#suppressLogs = suppressLogs ?? /^(1|true|on|yes)$/i.test(process.env.TRV_QUIET ?? '');
36
38
 
37
39
  this.info = {
38
40
  state: 'startup',
@@ -207,7 +209,9 @@ export class CompilerServer {
207
209
  }
208
210
  log.info(`State changed: ${this.info.state}`);
209
211
  } else if (event.type === 'log') {
210
- log.render(event.payload);
212
+ if (!this.#suppressLogs) {
213
+ log.render(event.payload);
214
+ }
211
215
  }
212
216
  if (this.isResetEvent(event)) {
213
217
  await this.#disconnectActive();
package/src/state.ts CHANGED
@@ -1,14 +1,14 @@
1
+ import fs from 'node:fs';
1
2
  import type { CompilerHost, SourceFile, CompilerOptions, Program, ScriptTarget } from 'typescript';
2
3
 
3
4
  import { path, ManifestModuleUtil, type ManifestModule, type ManifestRoot, type ManifestIndex, type ManifestModuleFolderType } from '@travetto/manifest';
4
5
  import type { TransformerManager } from '@travetto/transformer';
5
6
 
6
7
  import { CompilerUtil } from './util.ts';
7
- import type { CompileEmitError, CompileStateEntry } from './types.ts';
8
+ import type { CompileStateEntry } from './types.ts';
8
9
  import { CommonUtil } from './common.ts';
9
10
  import { tsProxy as ts, tsProxyInit } from './ts-proxy.ts';
10
11
 
11
-
12
12
  const TYPINGS_FOLDER_KEYS = new Set<ManifestModuleFolderType>(['$index', 'support', 'src', '$package']);
13
13
 
14
14
  export class CompilerState implements CompilerHost {
@@ -40,10 +40,37 @@ export class CompilerState implements CompilerHost {
40
40
  #program: Program;
41
41
 
42
42
  #readFile(sourceFile: string): string | undefined {
43
- return ts.sys.readFile(this.#sourceToEntry.get(sourceFile)?.sourceFile ?? sourceFile);
43
+ const location = this.#sourceToEntry.get(sourceFile)?.sourceFile ?? sourceFile;
44
+ try {
45
+ return ts.sys.readFile(location, 'utf8');
46
+ } catch {
47
+ try { return fs.readFileSync(location, 'utf8'); } catch { }
48
+ }
49
+ return undefined;
44
50
  }
45
51
 
46
- #writeExternalTypings(location: string, text: string, bom: boolean): void {
52
+ #writeFile(location: string, text: string, bom?: boolean): void {
53
+ try {
54
+ ts.sys.writeFile(location, text, bom);
55
+ } catch {
56
+ fs.mkdirSync(path.dirname(location), { recursive: true });
57
+ fs.writeFileSync(location, text, 'utf8');
58
+ }
59
+ }
60
+
61
+ #fileExists(location: string): boolean {
62
+ try {
63
+ return ts.sys.fileExists(location);
64
+ } catch { return fs.existsSync(location); }
65
+ }
66
+
67
+ #directoryExists(location: string): boolean {
68
+ try {
69
+ return ts.sys.directoryExists(location);
70
+ } catch { return fs.existsSync(location); }
71
+ }
72
+
73
+ #writeExternalTypings(location: string, text: string, bom?: boolean): void {
47
74
  let core = location.replace('.map', '');
48
75
  if (!this.#outputToEntry.has(core)) {
49
76
  core = core.replace(ManifestModuleUtil.TYPINGS_EXT_REGEX, ManifestModuleUtil.OUTPUT_EXT);
@@ -52,7 +79,7 @@ export class CompilerState implements CompilerHost {
52
79
  if (entry) {
53
80
  const relative = this.#manifestIndex.getFromSource(entry.sourceFile)?.relativeFile;
54
81
  if (relative && TYPINGS_FOLDER_KEYS.has(ManifestModuleUtil.getFolderKey(relative))) {
55
- ts.sys.writeFile(location.replace(this.#outputPath, this.#typingsPath), text, bom);
82
+ this.#writeFile(location.replace(this.#outputPath, this.#typingsPath), text, bom);
56
83
  }
57
84
  }
58
85
  }
@@ -60,7 +87,7 @@ export class CompilerState implements CompilerHost {
60
87
  async #initCompilerOptions(): Promise<CompilerOptions> {
61
88
  const tsconfigFile = CommonUtil.resolveWorkspace(this.#manifest, 'tsconfig.json');
62
89
  if (!ts.sys.fileExists(tsconfigFile)) {
63
- ts.sys.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }, null, 2));
90
+ this.#writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }, null, 2));
64
91
  }
65
92
 
66
93
  const { options } = ts.parseJsonSourceFileConfigFileContent(
@@ -72,9 +99,7 @@ export class CompilerState implements CompilerHost {
72
99
  return {
73
100
  ...options,
74
101
  noEmit: false,
75
- emitDeclarationOnly: false,
76
102
  allowJs: true,
77
- resolveJsonModule: true,
78
103
  sourceRoot: this.#manifest.workspace.path,
79
104
  rootDir: this.#manifest.workspace.path,
80
105
  moduleResolution: ts.ModuleResolutionKind.Bundler,
@@ -140,8 +165,9 @@ export class CompilerState implements CompilerHost {
140
165
  return this.getBySource(randomSource)!.sourceFile;
141
166
  }
142
167
 
143
- async createProgram(force = false): Promise<Program> {
168
+ async getProgram(force = false): Promise<Program> {
144
169
  if (force || !this.#program) {
170
+ await this.initializeTypescript();
145
171
  this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
146
172
  this.#transformerManager.init(this.#program.getTypeChecker());
147
173
  await CommonUtil.queueMacroTask();
@@ -149,36 +175,56 @@ export class CompilerState implements CompilerHost {
149
175
  return this.#program;
150
176
  }
151
177
 
152
- async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<CompileEmitError | undefined> {
178
+ async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<string[] | undefined> {
153
179
  const output = this.#sourceToEntry.get(sourceFile)?.outputFile;
154
180
  if (!output) {
155
181
  return;
156
182
  }
157
183
 
158
- const program = await this.createProgram(needsNewProgram);
159
- try {
160
- switch (ManifestModuleUtil.getFileType(sourceFile)) {
161
- case 'typings':
162
- case 'package-json':
163
- this.writeFile(output, this.readFile(sourceFile)!, false), undefined;
164
- break;
165
- case 'js':
166
- this.writeFile(output, ts.transpile(this.readFile(sourceFile)!, this.#compilerOptions), false);
167
- break;
168
- case 'ts': {
169
- const result = program.emit(
170
- program.getSourceFile(sourceFile)!,
171
- (...args) => this.writeFile(args[0], args[1], args[2]), undefined, false,
172
- this.#transformerManager.get()
173
- );
174
- return result?.diagnostics?.length ? result.diagnostics : undefined;
175
- }
184
+ switch (ManifestModuleUtil.getFileType(sourceFile)) {
185
+ case 'package-json': {
186
+ const text = this.readFile(sourceFile)!;
187
+ const finalText = CompilerUtil.rewritePackageJSON(this.#manifest, text);
188
+ const location = this.#tscOutputFileToOuptut.get(output) ?? output;
189
+ this.#writeFile(location, finalText);
190
+ this.#writeExternalTypings(location, finalText);
191
+ break;
176
192
  }
177
- } catch (error) {
178
- if (error instanceof Error) {
179
- return error;
180
- } else {
181
- throw error;
193
+ case 'js':
194
+ case 'typings': this.writeFile(output, this.readFile(sourceFile)!); break;
195
+ case 'ts': {
196
+ const program = await this.getProgram(needsNewProgram);
197
+ const tsSourceFile = program.getSourceFile(sourceFile)!;
198
+ program.emit(
199
+ tsSourceFile,
200
+ (...args) => this.writeFile(args[0], args[1], args[2]), undefined, false,
201
+ this.#transformerManager.get()
202
+ );
203
+ return [
204
+ ...program.getSemanticDiagnostics(tsSourceFile),
205
+ ...program.getSyntacticDiagnostics(tsSourceFile),
206
+ ...program.getDeclarationDiagnostics(tsSourceFile),
207
+ ]
208
+ .filter(d => d.category === ts.DiagnosticCategory.Error)
209
+ .map(diag => {
210
+ let message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
211
+ if (
212
+ message.includes("is not under 'rootDir'")
213
+ || message.includes("does not exist on type 'EnvDataCombinedType'")
214
+ || message.startsWith('Could not find a declaration file for module')
215
+ || message.startsWith("Cannot find module '@travetto")
216
+ || message.startsWith("This JSX tag requires the module path '@travetto")
217
+ || message.startsWith("JSX element implicitly has type 'any'")
218
+ ) {
219
+ return '';
220
+ }
221
+ if (diag.file) {
222
+ const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
223
+ message = `${line + 1}:${character + 1} -- ${message}`;
224
+ }
225
+ return message;
226
+ })
227
+ .filter(Boolean);
182
228
  }
183
229
  }
184
230
  }
@@ -271,32 +317,24 @@ export class CompilerState implements CompilerHost {
271
317
  getDefaultLibLocation(): string { return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions)); }
272
318
 
273
319
  fileExists(sourceFile: string): boolean {
274
- return this.#sourceToEntry.has(sourceFile) || ts.sys.fileExists(sourceFile);
320
+ return this.#sourceToEntry.has(sourceFile) || this.#fileExists(sourceFile);
275
321
  }
276
322
 
277
323
  directoryExists(sourceDirectory: string): boolean {
278
- return this.#sourceDirectory.has(sourceDirectory) || ts.sys.directoryExists(sourceDirectory);
324
+ return this.#sourceDirectory.has(sourceDirectory) || this.#directoryExists(sourceDirectory);
279
325
  }
280
326
 
281
- writeFile(
282
- outputFile: string,
283
- text: string,
284
- bom: boolean
285
- ): void {
286
- if (outputFile.endsWith('package.json')) {
287
- text = CompilerUtil.rewritePackageJSON(this.#manifest, text);
288
- }
289
-
327
+ writeFile(outputFile: string, text: string, bom?: boolean): void {
290
328
  // JSX runtime shenanigans
291
329
  text = text.replace(/support\/jsx-runtime"/g, 'support/jsx-runtime.js"');
292
330
 
293
331
  const location = this.#tscOutputFileToOuptut.get(outputFile) ?? outputFile;
294
332
 
295
- if (ManifestModuleUtil.TYPINGS_WITH_MAP_EXT_REGEX.test(outputFile) || outputFile.endsWith('package.json')) {
333
+ if (ManifestModuleUtil.TYPINGS_WITH_MAP_EXT_REGEX.test(outputFile)) {
296
334
  this.#writeExternalTypings(location, text, bom);
297
335
  }
298
336
 
299
- ts.sys.writeFile(location, text, bom);
337
+ return this.#writeFile(location, text, bom);
300
338
  }
301
339
 
302
340
  readFile(sourceFile: string): string | undefined {
package/src/ts-proxy.ts CHANGED
@@ -2,7 +2,8 @@
2
2
  import type ts from 'typescript';
3
3
 
4
4
  let state: typeof ts | undefined;
5
- export const tsProxyInit = (): Promise<unknown> => import('typescript').then(module => { state = module.default; });
5
+ let promise: Promise<unknown> | undefined;
6
+ export const tsProxyInit = (): Promise<unknown> => promise ??= import('typescript').then(module => { state = module.default; });
6
7
 
7
8
  export const tsProxy = new Proxy({}!, {
8
9
  get(_, prop: string): unknown {
package/src/types.ts CHANGED
@@ -1,13 +1,9 @@
1
- import type ts from 'typescript';
2
-
3
1
  import type { ChangeEventType, ManifestModule } from '@travetto/manifest';
4
2
 
5
3
  export type CompilerStateType = 'startup' | 'init' | 'compile-start' | 'compile-end' | 'watch-start' | 'watch-end' | 'reset' | 'closed';
6
4
  export type CompilerLogLevel = 'info' | 'debug' | 'warn' | 'error';
7
5
 
8
- export type CompileEmitError = Error | readonly ts.Diagnostic[];
9
- export type CompileEmitter = (file: string, newProgram?: boolean) => Promise<CompileEmitError | undefined>;
10
- export type CompileEmitEvent = { file: string, i: number, total: number, error?: CompileEmitError, duration: number };
6
+ export type CompileEmitEvent = { file: string, i: number, total: number, errors?: string[], duration: number };
11
7
  export type CompileStateEntry = { sourceFile: string, tscOutputFile: string, outputFile?: string, module: ManifestModule, import: string, moduleFile: string };
12
8
  export type CompilerWatchEvent = { action: ChangeEventType, file: string, entry: CompileStateEntry, moduleFile: string };
13
9
 
package/src/util.ts CHANGED
@@ -1,10 +1,5 @@
1
1
  import { ManifestModuleUtil, type ManifestRoot, type Package } from '@travetto/manifest';
2
2
 
3
- import type { CompileEmitError } from './types.ts';
4
- import { tsProxy as ts } from './ts-proxy.ts';
5
-
6
- const nativeCwd = process.cwd();
7
-
8
3
  /**
9
4
  * Standard utilities for compiler
10
5
  */
@@ -38,32 +33,6 @@ export class CompilerUtil {
38
33
  return JSON.stringify(pkg, null, 2);
39
34
  }
40
35
 
41
- /**
42
- * Build transpilation error
43
- * @param filename The name of the file
44
- * @param diagnostics The diagnostic errors
45
- */
46
- static buildTranspileError(filename: string, diagnostics: CompileEmitError): Error {
47
- if (diagnostics instanceof Error) {
48
- return diagnostics;
49
- }
50
-
51
- const errors: string[] = diagnostics.slice(0, 5).map(diag => {
52
- const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
53
- if (diag.file) {
54
- const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
55
- return ` @ ${diag.file.fileName.replace(nativeCwd, '.')}(${line + 1}, ${character + 1}): ${message}`;
56
- } else {
57
- return ` ${message}`;
58
- }
59
- });
60
-
61
- if (diagnostics.length > 5) {
62
- errors.push(`${diagnostics.length - 5} more ...`);
63
- }
64
- return new Error(`Transpiling ${filename.replace(nativeCwd, '.')} failed: \n${errors.join('\n')}`);
65
- }
66
-
67
36
  /**
68
37
  * Naive hashing
69
38
  */
package/src/watch.ts CHANGED
@@ -195,7 +195,7 @@ export class CompilerWatcher {
195
195
  return;
196
196
  }
197
197
  const full = path.resolve(toolRootFolder, file);
198
- const stat = await fs.stat(full).catch(() => null);
198
+ const stat = await fs.stat(full, { throwIfNoEntry: false });
199
199
  if (toolFolders.has(full) && !stat) {
200
200
  this.#queue.throw(new CompilerReset(`Tooling folder removal ${full}`));
201
201
  }
@@ -229,7 +229,7 @@ export class CompilerWatcher {
229
229
 
230
230
  async #listenGitChanges(): Promise<void> {
231
231
  const gitFolder = path.resolve(this.#root, '.git');
232
- if (!await fs.stat(gitFolder).catch(() => false)) { return; }
232
+ if (!await fs.stat(gitFolder, { throwIfNoEntry: false })) { return; }
233
233
  log.debug('Starting git canary');
234
234
  const listener = watch(gitFolder, { encoding: 'utf8' }, async (event, file) => {
235
235
  if (!file) {
package/support/invoke.ts CHANGED
@@ -7,40 +7,62 @@ import { CompilerClient } from '../src/server/client.ts';
7
7
  import { CommonUtil } from '../src/common.ts';
8
8
  import { EventUtil } from '../src/event.ts';
9
9
 
10
- const HELP = `
11
- trvc [command]
10
+ const hasColor = (process.stdout.isTTY && /^(0)*$/.test(process.env.NO_COLOR ?? '')) || /1\d*/.test(process.env.FORCE_COLOR ?? '');
11
+ const color = (code: number) => (value: string): string => hasColor ? `\x1b[${code}m${value}\x1b[0m` : `${value}`;
12
+ const STYLE = { error: color(91), title: color(36), main: color(92), command: color(35), arg: color(37), description: color(33), };
12
13
 
13
- Available Commands:
14
- * start|watch - Run the compiler in watch mode
15
- * stop - Stop the compiler if running
16
- * restart - Restart the compiler in watch mode
17
- * build - Ensure the project is built and upto date
18
- * clean - Clean out the output and compiler caches
19
- * info - Retrieve the compiler information, if running
20
- * event <log|progress|state> - Watch events in realtime as newline delimited JSON
21
- * exec <file> [...args] - Allow for compiling and executing an entrypoint file
22
- * manifest [output] - Generate the project manifest
23
- * manifest:production [output] - Generate the production project manifest
24
- `;
14
+ const COMMANDS = {
15
+ start: { description: 'Run the compiler in watch mode' },
16
+ stop: { description: 'Stop the compiler if running' },
17
+ restart: { description: 'Restart the compiler in watch mode' },
18
+ build: { description: 'Ensure the project is built and upto date' },
19
+ clean: { description: 'Clean out the output and compiler caches' },
20
+ info: { description: 'Retrieve the compiler information, if running' },
21
+ event: { args: ['<log|progress|state>'], description: 'Watch events in realtime as newline delimited JSON' },
22
+ exec: { args: ['<file>', '[...args]'], description: 'Allow for compiling and executing an entrypoint file' },
23
+ manifest: { args: ['[output]'], description: 'Generate the project manifest' },
24
+ 'manifest:production': { args: ['[output]'], description: 'Generate the production project manifest' }
25
+ } as const;
26
+
27
+ function showHelp(errorMessage?: string): void {
28
+ const PREPARED = Object.entries(COMMANDS)
29
+ .map(([name, config]) => ({ args: [], ...config, name }))
30
+ .map(config => ({ ...config, commandLength: [config.name, ...config.args].join(' ').length }));
31
+
32
+ const commandWidth = Math.max(...PREPARED.map(config => config.commandLength));
33
+
34
+ console.log([
35
+ ...(errorMessage ? ['', STYLE.error(errorMessage)] : []),
36
+ '', `${STYLE.main('trvc')} ${STYLE.command('[command]')}`,
37
+ '', STYLE.title('Available Commands'),
38
+ ...PREPARED.map(({ name, args, description, commandLength }) => [
39
+ '*', STYLE.command(name), ...args.map(arg => STYLE.arg(arg)),
40
+ ' '.repeat(commandWidth - commandLength), '-', STYLE.description(description)
41
+
42
+ ].join(' ')),
43
+ ''
44
+ ].join('\n'));
45
+ }
46
+
47
+ const validateInputs = (value: string[]): value is [keyof typeof COMMANDS, ...string[]] => !!value.length && value[0] in COMMANDS;
25
48
 
26
49
  /**
27
50
  * Invoke the compiler
28
51
  */
29
- export async function invoke(operation?: string, args: string[] = []): Promise<unknown> {
30
- if (operation === undefined) {
31
- [operation, ...args] = process.argv.slice(2);
52
+ export async function invoke(...input: string[]): Promise<unknown> {
53
+ if (!validateInputs(input)) {
54
+ return showHelp(input[0] ? `Unknown trvc command: ${input[0]}` : undefined);;
32
55
  }
56
+
57
+ const [command, ...args] = input;
33
58
  const ctx = getManifestContext();
34
59
  const client = new CompilerClient(ctx, Log.scoped('client'));
35
60
 
36
61
  Log.initLevel('error');
37
62
  Log.root = ctx.workspace.path;
38
63
 
39
- switch (operation) {
40
- case undefined:
41
- case 'help': console.log(HELP); break;
42
- case 'start':
43
- case 'watch': return CompilerManager.compile(ctx, client, { watch: true });
64
+ switch (command) {
65
+ case 'start': return CompilerManager.compile(ctx, client, { watch: true });
44
66
  case 'build': return CompilerManager.compile(ctx, client, { watch: false });
45
67
  case 'restart': return CompilerManager.compile(ctx, client, { watch: true, forceRestart: true });
46
68
  case 'info': {
@@ -59,7 +81,7 @@ export async function invoke(operation?: string, args: string[] = []): Promise<u
59
81
  case 'manifest:production':
60
82
  case 'manifest': {
61
83
  let manifest = await ManifestUtil.buildManifest(ctx);
62
- if (operation === 'manifest:production') {
84
+ if (command === 'manifest:production') {
63
85
  manifest = ManifestUtil.createProductionManifest(manifest);
64
86
  }
65
87
  if (args[0]) {
@@ -92,6 +114,5 @@ export async function invoke(operation?: string, args: string[] = []): Promise<u
92
114
  // Return function to run import on a module
93
115
  return import(importTarget);
94
116
  }
95
- default: console.error(`\nUnknown trvc operation: ${operation}\n${HELP}`);
96
117
  }
97
118
  }
package/tsconfig.trv.json CHANGED
@@ -7,17 +7,22 @@
7
7
  "lib": [
8
8
  "ESNext",
9
9
  ],
10
+ "types": [
11
+ "node"
12
+ ],
10
13
  "jsx": "react-jsx",
11
14
  "stableTypeOrdering": true,
12
15
  "strict": true,
13
16
  "declaration": true,
14
17
  "declarationMap": true,
18
+ "emitDeclarationOnly": false,
15
19
  "noUncheckedSideEffectImports": true,
16
20
  "libReplacement": false,
17
21
  "strictPropertyInitialization": false,
18
22
  "erasableSyntaxOnly": true,
19
23
  "experimentalDecorators": true,
20
24
  "noEmitOnError": false,
25
+ "allowJs": true,
21
26
  "noErrorTruncation": true,
22
27
  "resolveJsonModule": true,
23
28
  "sourceMap": true,