@travetto/compiler 8.0.0-alpha.2 → 8.0.0-alpha.3

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
@@ -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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "8.0.0-alpha.2",
3
+ "version": "8.0.0-alpha.3",
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.2",
35
- "@travetto/transformer": "^8.0.0-alpha.2"
34
+ "@travetto/manifest": "^8.0.0-alpha.3",
35
+ "@travetto/transformer": "^8.0.0-alpha.3"
36
36
  },
37
37
  "peerDependencies": {
38
- "@travetto/cli": "^8.0.0-alpha.3"
38
+ "@travetto/cli": "^8.0.0-alpha.4"
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';
@@ -50,7 +49,7 @@ export class Compiler {
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;
@@ -110,30 +108,21 @@ 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' });
@@ -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,40 @@ 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
+ await fs.utimes(event.file, new Date(), new Date());
182
168
  }
183
169
  metrics.push(event);
184
170
  }
185
171
  if (this.#signal.aborted) {
186
172
  log.debug('Compilation aborted');
187
- } else if (failure) {
188
- log.debug('Compilation failed');
189
- return this.#shutdown('error', failure);
173
+ } else if (failures.size) {
174
+ const sortedFailures = [...failures.entries()].sort((a, b) => a[0].localeCompare(b[0]));
175
+ log.debug('Compilation failed',
176
+ ['', sortedFailures.flatMap(([file, count]) => `- ${file}: ${count} errors found`)]
177
+ .flat(3).join('\n')
178
+ );
190
179
  } else {
191
180
  log.debug('Compilation succeeded');
192
181
  }
193
182
 
194
- // Rebuild manifests if dirty
183
+ // Rebuild manifests
195
184
  const manifest = await ManifestUtil.buildManifest(this.#state.manifestIndex.manifest);
196
185
  await ManifestUtil.writeManifest(manifest);
197
186
  await ManifestUtil.writeDependentManifests(manifest);
187
+
188
+ if (failures.size) {
189
+ return this.#shutdown('error');
190
+ }
191
+
198
192
  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
193
  }
204
194
 
205
195
  EventUtil.sendEvent('state', { state: 'compile-end' });
@@ -209,15 +199,18 @@ export class Compiler {
209
199
  }
210
200
 
211
201
  if (this.#watch && !this.#signal.aborted) {
202
+ const resolved = this.#state.getArbitraryInputFile();
203
+ await this.#state.compileSourceFile(resolved);
204
+
212
205
  log.info('Watch is ready');
213
206
 
214
207
  EventUtil.sendEvent('state', { state: 'watch-start' });
215
208
  try {
216
209
  for await (const event of new CompilerWatcher(this.#state, this.#signal)) {
217
210
  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));
211
+ const errors = await this.#state.compileSourceFile(event.entry.sourceFile, true);
212
+ if (errors?.length) {
213
+ log.error('Compilation failed', `${event.entry.sourceFile}: ${errors.length} errors found`);
221
214
  } else {
222
215
  log.info(`Compiled ${event.entry.sourceFile} on ${event.action}`);
223
216
  }
@@ -242,7 +235,7 @@ export class Compiler {
242
235
  EventUtil.sendEvent('state', { state: 'watch-end' });
243
236
  } catch (error) {
244
237
  if (error instanceof Error) {
245
- this.#shutdown(error instanceof CompilerReset ? 'reset' : 'error', error);
238
+ this.#shutdown(error instanceof CompilerReset ? 'reset' : 'error', error.message);
246
239
  }
247
240
  }
248
241
  }
@@ -251,4 +244,4 @@ export class Compiler {
251
244
 
252
245
  this.#shutdown('complete');
253
246
  }
254
- }
247
+ }
@@ -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
 
package/src/state.ts CHANGED
@@ -4,11 +4,10 @@ import { path, ManifestModuleUtil, type ManifestModule, type ManifestRoot, type
4
4
  import type { TransformerManager } from '@travetto/transformer';
5
5
 
6
6
  import { CompilerUtil } from './util.ts';
7
- import type { CompileEmitError, CompileStateEntry } from './types.ts';
7
+ import type { CompileStateEntry } from './types.ts';
8
8
  import { CommonUtil } from './common.ts';
9
9
  import { tsProxy as ts, tsProxyInit } from './ts-proxy.ts';
10
10
 
11
-
12
11
  const TYPINGS_FOLDER_KEYS = new Set<ManifestModuleFolderType>(['$index', 'support', 'src', '$package']);
13
12
 
14
13
  export class CompilerState implements CompilerHost {
@@ -72,9 +71,7 @@ export class CompilerState implements CompilerHost {
72
71
  return {
73
72
  ...options,
74
73
  noEmit: false,
75
- emitDeclarationOnly: false,
76
74
  allowJs: true,
77
- resolveJsonModule: true,
78
75
  sourceRoot: this.#manifest.workspace.path,
79
76
  rootDir: this.#manifest.workspace.path,
80
77
  moduleResolution: ts.ModuleResolutionKind.Bundler,
@@ -140,8 +137,9 @@ export class CompilerState implements CompilerHost {
140
137
  return this.getBySource(randomSource)!.sourceFile;
141
138
  }
142
139
 
143
- async createProgram(force = false): Promise<Program> {
140
+ async getProgram(force = false): Promise<Program> {
144
141
  if (force || !this.#program) {
142
+ await this.initializeTypescript();
145
143
  this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
146
144
  this.#transformerManager.init(this.#program.getTypeChecker());
147
145
  await CommonUtil.queueMacroTask();
@@ -149,36 +147,46 @@ export class CompilerState implements CompilerHost {
149
147
  return this.#program;
150
148
  }
151
149
 
152
- async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<CompileEmitError | undefined> {
150
+ async compileSourceFile(sourceFile: string, needsNewProgram = false): Promise<string[] | undefined> {
153
151
  const output = this.#sourceToEntry.get(sourceFile)?.outputFile;
154
152
  if (!output) {
155
153
  return;
156
154
  }
157
155
 
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
- }
176
- }
177
- } catch (error) {
178
- if (error instanceof Error) {
179
- return error;
180
- } else {
181
- throw error;
156
+ const program = await this.getProgram(needsNewProgram);
157
+ switch (ManifestModuleUtil.getFileType(sourceFile)) {
158
+ case 'typings':
159
+ case 'package-json':
160
+ this.writeFile(output, this.readFile(sourceFile)!, false), undefined;
161
+ break;
162
+ case 'js':
163
+ this.writeFile(output, ts.transpile(this.readFile(sourceFile)!, this.#compilerOptions), false);
164
+ break;
165
+ case 'ts': {
166
+ const tsSourceFile = program.getSourceFile(sourceFile)!;
167
+ program.emit(
168
+ tsSourceFile,
169
+ (...args) => this.writeFile(args[0], args[1], args[2]), undefined, false,
170
+ this.#transformerManager.get()
171
+ );
172
+ return [
173
+ ...program.getSemanticDiagnostics(tsSourceFile),
174
+ ...program.getSyntacticDiagnostics(tsSourceFile),
175
+ ...program.getDeclarationDiagnostics(tsSourceFile),
176
+ ]
177
+ .filter(d => d.category === ts.DiagnosticCategory.Error)
178
+ .map(diag => {
179
+ let message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
180
+ if (message.includes('rootDir') || message.includes('EnvDataCombinedType')) {
181
+ return '';
182
+ }
183
+ if (diag.file) {
184
+ const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
185
+ message = `${line + 1}:${character + 1} -- ${message}`;
186
+ }
187
+ return message;
188
+ })
189
+ .filter(Boolean);
182
190
  }
183
191
  }
184
192
  }
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/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,