@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 +2 -2
- package/bin/hook.js +5 -14
- package/bin/trvc-target.js +1 -0
- package/bin/trvc.js +2 -1
- package/package.json +4 -4
- package/src/compiler.ts +53 -55
- package/src/server/manager.ts +3 -2
- package/src/server/server.ts +6 -2
- package/src/state.ts +84 -46
- package/src/ts-proxy.ts +2 -1
- package/src/types.ts +1 -5
- package/src/util.ts +0 -31
- package/src/watch.ts +2 -2
- package/support/invoke.ts +45 -24
- package/tsconfig.trv.json +5 -0
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
|
|
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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
+
});
|
package/bin/trvc-target.js
CHANGED
package/bin/trvc.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/compiler",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
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.
|
|
35
|
-
"@travetto/transformer": "^8.0.0-alpha.
|
|
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.
|
|
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,
|
|
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
|
-
#
|
|
36
|
-
#
|
|
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.#
|
|
46
|
-
this.#
|
|
47
|
-
setMaxListeners(1000, this.#
|
|
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',
|
|
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 (
|
|
68
|
-
|
|
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',
|
|
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.#
|
|
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[]
|
|
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
|
|
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,
|
|
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.#
|
|
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
|
|
161
|
-
let failure: Error | undefined;
|
|
149
|
+
const failures = new Map<string, number>();
|
|
162
150
|
|
|
163
|
-
log.debug(
|
|
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
|
|
178
|
-
if (event.
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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.#
|
|
173
|
+
if (this.#shutdownSignal.aborted) {
|
|
186
174
|
log.debug('Compilation aborted');
|
|
187
|
-
} else if (
|
|
188
|
-
|
|
189
|
-
|
|
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
|
|
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.#
|
|
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.#
|
|
211
|
+
for await (const event of new CompilerWatcher(this.#state, this.#shutdownSignal)) {
|
|
217
212
|
if (event.action !== 'delete') {
|
|
218
|
-
const
|
|
219
|
-
if (
|
|
220
|
-
log.
|
|
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
|
+
}
|
package/src/server/manager.ts
CHANGED
|
@@ -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'
|
|
80
|
+
await client.waitForState(['compile-end'], 'Successfully built');
|
|
80
81
|
controller.abort();
|
|
81
82
|
}
|
|
82
83
|
}
|
package/src/server/server.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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<
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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) ||
|
|
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) ||
|
|
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)
|
|
333
|
+
if (ManifestModuleUtil.TYPINGS_WITH_MAP_EXT_REGEX.test(outputFile)) {
|
|
296
334
|
this.#writeExternalTypings(location, text, bom);
|
|
297
335
|
}
|
|
298
336
|
|
|
299
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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(
|
|
30
|
-
if (
|
|
31
|
-
[
|
|
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 (
|
|
40
|
-
case
|
|
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 (
|
|
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,
|