@travetto/compiler 3.3.2 → 3.4.0-rc.1
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 +42 -44
- package/bin/{trv.js → common.js} +10 -30
- package/bin/trvc.js +35 -0
- package/package.json +8 -7
- package/src/compiler.ts +40 -24
- package/src/event.ts +7 -0
- package/src/internal/watch-core.ts +104 -0
- package/src/log.ts +8 -9
- package/src/state.ts +4 -4
- package/src/types.ts +0 -6
- package/src/watch.ts +58 -97
- package/support/entry.trvc.ts +73 -0
- package/support/log.ts +46 -28
- package/support/queue.ts +41 -0
- package/support/server/client.ts +118 -0
- package/support/server/runner.ts +107 -0
- package/support/server/server.ts +189 -0
- package/support/setup.ts +229 -0
- package/support/types.ts +27 -0
- package/support/util.ts +108 -0
- package/tsconfig.trv.json +1 -2
- package/support/launcher.ts +0 -160
- package/support/lock-pinger.ts +0 -25
- package/support/lock.ts +0 -234
- package/support/transpile.ts +0 -256
- /package/support/{compiler-entry.ts → entry.compiler.ts} +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
6
|
+
|
|
7
|
+
import type { CompilerMode, CompilerOp, CompilerServerEvent, CompilerServerEventType, CompilerServerInfo } from '../types';
|
|
8
|
+
import { LogUtil } from '../log';
|
|
9
|
+
import { CompilerClientUtil } from './client';
|
|
10
|
+
import { CommonUtil } from '../util';
|
|
11
|
+
|
|
12
|
+
const log = LogUtil.log.bind(LogUtil, 'compiler-server');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Compiler Server Class
|
|
16
|
+
*/
|
|
17
|
+
export class CompilerServer {
|
|
18
|
+
|
|
19
|
+
#ctx: ManifestContext;
|
|
20
|
+
#server: http.Server;
|
|
21
|
+
#listeners: { res: http.ServerResponse, type: CompilerServerEventType }[] = [];
|
|
22
|
+
#shutdown = new AbortController();
|
|
23
|
+
signal = this.#shutdown.signal;
|
|
24
|
+
info: CompilerServerInfo;
|
|
25
|
+
|
|
26
|
+
constructor(ctx: ManifestContext, op: CompilerOp) {
|
|
27
|
+
this.#ctx = ctx;
|
|
28
|
+
this.info = {
|
|
29
|
+
state: 'startup',
|
|
30
|
+
iteration: Date.now(),
|
|
31
|
+
mode: op === 'run' ? 'build' : op,
|
|
32
|
+
serverPid: process.pid,
|
|
33
|
+
compilerPid: -1,
|
|
34
|
+
path: ctx.workspacePath,
|
|
35
|
+
url: ctx.compilerUrl
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
this.#server = http.createServer({
|
|
39
|
+
keepAlive: true,
|
|
40
|
+
requestTimeout: 1000 * 60 * 60,
|
|
41
|
+
keepAliveTimeout: 1000 * 60 * 60,
|
|
42
|
+
}, (req, res) => this.#onRequest(req, res));
|
|
43
|
+
|
|
44
|
+
// Connect
|
|
45
|
+
process.on('SIGINT', () => this.#shutdown.abort());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get mode(): CompilerMode {
|
|
49
|
+
return this.info.mode;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
isResetEvent(ev: CompilerServerEvent): boolean {
|
|
53
|
+
return ev.type === 'state' && ev.payload.state === 'reset';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async #tryListen(attempt = 0): Promise<'ok' | 'running'> {
|
|
57
|
+
const output = await new Promise<'ok' | 'running' | 'retry'>((resolve, reject) => {
|
|
58
|
+
this.#server
|
|
59
|
+
.on('listening', () => resolve('ok'))
|
|
60
|
+
.on('error', async err => {
|
|
61
|
+
if ('code' in err && err.code === 'EADDRINUSE') {
|
|
62
|
+
const info = await CompilerClientUtil.getServerInfo(this.#ctx);
|
|
63
|
+
resolve((info && info.mode === 'build' && this.mode === 'watch') ? 'retry' : 'running');
|
|
64
|
+
} else {
|
|
65
|
+
reject(err);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const url = new URL(this.#ctx.compilerUrl);
|
|
70
|
+
setTimeout(() => this.#server.listen(+url.port, url.hostname), 1); // Run async
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (output === 'retry') {
|
|
74
|
+
if (attempt >= 5) {
|
|
75
|
+
throw new Error('Unable to verify compilation server');
|
|
76
|
+
}
|
|
77
|
+
log('info', 'Waiting for build to finish, before retrying');
|
|
78
|
+
// Let the server finish
|
|
79
|
+
await CompilerClientUtil.waitForState(this.#ctx, ['close'], this.signal);
|
|
80
|
+
return this.#tryListen(attempt + 1);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return output;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async #addListener(type: string, res: http.ServerResponse): Promise<void> {
|
|
87
|
+
res.writeHead(200);
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
89
|
+
this.#listeners.push({ res, type: type as 'change' });
|
|
90
|
+
await new Promise(resolve => res.on('close', resolve));
|
|
91
|
+
this.#listeners.splice(this.#listeners.findIndex(x => x.res === res), 1);
|
|
92
|
+
res.end();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#emitEvent(ev: CompilerServerEvent): void {
|
|
96
|
+
const msg = `${JSON.stringify(ev.payload)}\n`;
|
|
97
|
+
for (const el of this.#listeners) {
|
|
98
|
+
if (!el.res.closed && el.type === ev.type) {
|
|
99
|
+
el.res.write(msg);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #disconnectActive(): Promise<void> {
|
|
105
|
+
log('info', 'Server disconnect requested');
|
|
106
|
+
this.info.iteration = Date.now();
|
|
107
|
+
await new Promise(r => setTimeout(r, 20));
|
|
108
|
+
this.#server.closeAllConnections(); // Force reconnects
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async #clean(): Promise<{ clean: boolean }> {
|
|
112
|
+
await Promise.all([this.#ctx.compilerFolder, this.#ctx.outputFolder]
|
|
113
|
+
.map(f => fs.rm(path.resolve(this.#ctx.workspacePath, f), { recursive: true, force: true })));
|
|
114
|
+
return { clean: true };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Request handler
|
|
119
|
+
*/
|
|
120
|
+
async #onRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
121
|
+
res.setHeader('Content-Type', 'application/json');
|
|
122
|
+
|
|
123
|
+
const [, action, subAction] = new URL(`${this.#ctx.compilerUrl}${req.url}`).pathname.split('/');
|
|
124
|
+
|
|
125
|
+
log('debug', 'Receive request', { action, subAction });
|
|
126
|
+
|
|
127
|
+
let out: unknown;
|
|
128
|
+
switch (action) {
|
|
129
|
+
case 'event': return await this.#addListener(subAction, res);
|
|
130
|
+
case 'stop': out = await this.close(); break;
|
|
131
|
+
case 'clean': out = await this.#clean(); break;
|
|
132
|
+
case 'info':
|
|
133
|
+
default: out = this.info ?? {}; break;
|
|
134
|
+
}
|
|
135
|
+
res.end(JSON.stringify(out));
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Process events
|
|
140
|
+
*/
|
|
141
|
+
async processEvents(src: (signal: AbortSignal) => AsyncIterable<CompilerServerEvent>): Promise<void> {
|
|
142
|
+
|
|
143
|
+
CompilerClientUtil.streamLogs(this.#ctx, this.signal); // Send logs to stdout
|
|
144
|
+
|
|
145
|
+
for await (const ev of CommonUtil.restartableEvents(src, this.signal, this.isResetEvent)) {
|
|
146
|
+
this.#emitEvent(ev);
|
|
147
|
+
|
|
148
|
+
if (ev.type === 'state') {
|
|
149
|
+
this.info.state = ev.payload.state;
|
|
150
|
+
if (ev.payload.state === 'init' && ev.payload.extra && 'pid' in ev.payload.extra && typeof ev.payload.extra.pid === 'number') {
|
|
151
|
+
this.info.compilerPid = ev.payload.extra.pid;
|
|
152
|
+
}
|
|
153
|
+
log('info', `State changed: ${this.info.state}`);
|
|
154
|
+
}
|
|
155
|
+
if (this.isResetEvent(ev)) {
|
|
156
|
+
await this.#disconnectActive();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Terminate, after letting all remaining events emit
|
|
161
|
+
setImmediate(() => this.close());
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Close server
|
|
166
|
+
*/
|
|
167
|
+
async close(): Promise<unknown> {
|
|
168
|
+
log('info', 'Closing down server');
|
|
169
|
+
setTimeout(async () => {
|
|
170
|
+
this.#shutdown.abort();
|
|
171
|
+
this.#emitEvent({ type: 'state', payload: { state: 'close' } });
|
|
172
|
+
this.#server.unref();
|
|
173
|
+
await new Promise(r => {
|
|
174
|
+
this.#server.close(r);
|
|
175
|
+
setImmediate(() => this.#server.closeAllConnections());
|
|
176
|
+
});
|
|
177
|
+
}, 10);
|
|
178
|
+
return { closing: true };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Start the server listening
|
|
183
|
+
*/
|
|
184
|
+
async listen(): Promise<CompilerServer | undefined> {
|
|
185
|
+
const running = await this.#tryListen() === 'ok';
|
|
186
|
+
log('info', running ? 'Starting server' : 'Server already running under a different process', this.#ctx.compilerUrl);
|
|
187
|
+
return running ? this : undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
package/support/setup.ts
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
import { type DeltaEvent, type ManifestContext, type ManifestRoot, Package } from '@travetto/manifest';
|
|
5
|
+
|
|
6
|
+
import { LogUtil } from './log';
|
|
7
|
+
import { CommonUtil } from './util';
|
|
8
|
+
|
|
9
|
+
type ModFile = { input: string, output: string, stale: boolean };
|
|
10
|
+
|
|
11
|
+
const SOURCE_SEED = ['package.json', 'index.ts', '__index__.ts', 'src', 'support', 'bin'];
|
|
12
|
+
const PRECOMPILE_MODS = ['@travetto/terminal', '@travetto/manifest', '@travetto/transformer', '@travetto/compiler'];
|
|
13
|
+
const RECENT_STAT = (stat: { ctimeMs: number, mtimeMs: number }): number => Math.max(stat.ctimeMs, stat.mtimeMs);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Compiler Setup Utilities
|
|
17
|
+
*/
|
|
18
|
+
export class CompilerSetup {
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Import a compiled manifest
|
|
22
|
+
*/
|
|
23
|
+
static #importManifest = (ctx: ManifestContext): Promise<typeof import('@travetto/manifest')> =>
|
|
24
|
+
import(path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/manifest/__index__.js'));
|
|
25
|
+
|
|
26
|
+
/** Convert a file to a given ext */
|
|
27
|
+
static #sourceToExtension(inputFile: string, ext: string): string {
|
|
28
|
+
return inputFile.replace(/[.][tj]sx?$/, ext);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get the output file name for a given input
|
|
33
|
+
*/
|
|
34
|
+
static #sourceToOutputExt(inputFile: string): string {
|
|
35
|
+
return this.#sourceToExtension(inputFile, '.js');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Output a file, support for ts, js, and package.json
|
|
40
|
+
*/
|
|
41
|
+
static async #transpileFile(ctx: ManifestContext, inputFile: string, outputFile: string): Promise<void> {
|
|
42
|
+
const type = CommonUtil.getFileType(inputFile);
|
|
43
|
+
if (type === 'js' || type === 'ts') {
|
|
44
|
+
const compilerOut = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules');
|
|
45
|
+
|
|
46
|
+
const text = (await fs.readFile(inputFile, 'utf8'))
|
|
47
|
+
.replace(/from '([.][^']+)'/g, (_, i) => `from '${i.replace(/[.]js$/, '')}.js'`)
|
|
48
|
+
.replace(/from '(@travetto\/(.*?))'/g, (_, i, s) => `from '${path.resolve(compilerOut, `${i}${s.includes('/') ? '.js' : '/__index__.js'}`)}'`);
|
|
49
|
+
|
|
50
|
+
const ts = (await import('typescript')).default;
|
|
51
|
+
const content = ts.transpile(text, {
|
|
52
|
+
...await CommonUtil.getCompilerOptions(ctx),
|
|
53
|
+
sourceMap: false,
|
|
54
|
+
inlineSourceMap: true,
|
|
55
|
+
}, inputFile);
|
|
56
|
+
await CommonUtil.writeTextFile(outputFile, content);
|
|
57
|
+
} else if (type === 'package-json') {
|
|
58
|
+
const pkg: Package = JSON.parse(await fs.readFile(inputFile, 'utf8'));
|
|
59
|
+
const main = pkg.main ? this.#sourceToOutputExt(pkg.main) : undefined;
|
|
60
|
+
const files = pkg.files?.map(x => this.#sourceToOutputExt(x));
|
|
61
|
+
|
|
62
|
+
const content = JSON.stringify({ ...pkg, main, type: ctx.moduleType, files }, null, 2);
|
|
63
|
+
await CommonUtil.writeTextFile(outputFile, content);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Scan directory to find all project sources for comparison
|
|
69
|
+
*/
|
|
70
|
+
static async #getModuleSources(ctx: ManifestContext, module: string, seed: string[]): Promise<ModFile[]> {
|
|
71
|
+
const inputFolder = (ctx.mainModule === module) ?
|
|
72
|
+
process.cwd() :
|
|
73
|
+
CommonUtil.resolveModuleFolder(module);
|
|
74
|
+
|
|
75
|
+
const folders = seed.filter(x => !/[.]/.test(x)).map(x => path.resolve(inputFolder, x));
|
|
76
|
+
const files = seed.filter(x => /[.]/.test(x)).map(x => path.resolve(inputFolder, x));
|
|
77
|
+
|
|
78
|
+
while (folders.length) {
|
|
79
|
+
const sub = folders.pop();
|
|
80
|
+
if (!sub) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const file of await fs.readdir(sub).catch(() => [])) {
|
|
85
|
+
if (file.startsWith('.')) {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
const resolvedInput = path.resolve(sub, file);
|
|
89
|
+
const stat = await fs.stat(resolvedInput);
|
|
90
|
+
|
|
91
|
+
if (stat.isDirectory()) {
|
|
92
|
+
folders.push(resolvedInput);
|
|
93
|
+
} else {
|
|
94
|
+
switch (CommonUtil.getFileType(file)) {
|
|
95
|
+
case 'js': case 'ts': files.push(resolvedInput);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const outputFolder = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', module);
|
|
102
|
+
const out: ModFile[] = [];
|
|
103
|
+
for (const input of files) {
|
|
104
|
+
const output = this.#sourceToOutputExt(input.replace(inputFolder, outputFolder));
|
|
105
|
+
const inputTs = await fs.stat(input).then(RECENT_STAT, () => 0);
|
|
106
|
+
if (inputTs) {
|
|
107
|
+
const outputTs = await fs.stat(output).then(RECENT_STAT, () => 0);
|
|
108
|
+
await fs.mkdir(path.dirname(output), { recursive: true, });
|
|
109
|
+
out.push({ input, output, stale: inputTs > outputTs });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Recompile folder if stale
|
|
118
|
+
*/
|
|
119
|
+
static async #compileIfStale(ctx: ManifestContext, scope: string, mod: string, seed: string[]): Promise<string[]> {
|
|
120
|
+
const files = await this.#getModuleSources(ctx, mod, seed);
|
|
121
|
+
const changes = files.filter(x => x.stale).map(x => x.input);
|
|
122
|
+
const out: string[] = [];
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await LogUtil.withLogger(scope, async log => {
|
|
126
|
+
if (files.some(f => f.stale)) {
|
|
127
|
+
log('debug', 'Starting', mod);
|
|
128
|
+
for (const file of files.filter(x => x.stale)) {
|
|
129
|
+
await this.#transpileFile(ctx, file.input, file.output);
|
|
130
|
+
}
|
|
131
|
+
if (changes.length) {
|
|
132
|
+
out.push(...changes.map(x => `${mod}/${x}`));
|
|
133
|
+
log('debug', `Source changed: ${changes.join(', ')}`, mod);
|
|
134
|
+
}
|
|
135
|
+
log('debug', 'Completed', mod);
|
|
136
|
+
} else {
|
|
137
|
+
log('debug', 'Skipped', mod);
|
|
138
|
+
}
|
|
139
|
+
}, false);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error(err);
|
|
142
|
+
}
|
|
143
|
+
return out;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Export manifest
|
|
148
|
+
*/
|
|
149
|
+
static async exportManifest(ctx: ManifestContext, output?: string, env = 'dev'): Promise<void> {
|
|
150
|
+
const { ManifestUtil } = await this.#importManifest(ctx);
|
|
151
|
+
let manifest = await ManifestUtil.buildManifest(ctx);
|
|
152
|
+
|
|
153
|
+
// If in prod mode, only include std modules
|
|
154
|
+
if (/^prod/i.test(env)) {
|
|
155
|
+
manifest = ManifestUtil.createProductionManifest(manifest);
|
|
156
|
+
}
|
|
157
|
+
if (output) {
|
|
158
|
+
output = await ManifestUtil.writeManifestToFile(output, manifest);
|
|
159
|
+
} else {
|
|
160
|
+
console.log(JSON.stringify(manifest, null, 2));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Sets up compiler, and produces a manifest and set of changes that need to be processed
|
|
166
|
+
*/
|
|
167
|
+
static async setup(ctx: ManifestContext): Promise<{ manifest: ManifestRoot, changed: DeltaEvent[] }> {
|
|
168
|
+
let changes = 0;
|
|
169
|
+
|
|
170
|
+
await LogUtil.withLogger('precompile', async () => {
|
|
171
|
+
for (const mod of PRECOMPILE_MODS) {
|
|
172
|
+
const count = (await this.#compileIfStale(ctx, 'precompile', mod, SOURCE_SEED)).length;
|
|
173
|
+
if (mod !== '@travetto/terminal') {
|
|
174
|
+
changes += count;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const { ManifestUtil, ManifestDeltaUtil, PackageUtil } = await this.#importManifest(ctx);
|
|
180
|
+
|
|
181
|
+
PackageUtil.clearCache();
|
|
182
|
+
|
|
183
|
+
const manifest = await LogUtil.withLogger('manifest', () => ManifestUtil.buildManifest(ctx));
|
|
184
|
+
|
|
185
|
+
await LogUtil.withLogger('transformers', async () => {
|
|
186
|
+
for (const mod of Object.values(manifest.modules).filter(m => m.files.$transformer?.length)) {
|
|
187
|
+
changes += (await this.#compileIfStale(ctx, 'transformers', mod.name, ['package.json', ...mod.files.$transformer!.map(x => x[0])])).length;
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
const delta = await LogUtil.withLogger('delta', async log => {
|
|
192
|
+
if (changes) {
|
|
193
|
+
log('debug', 'Skipping, everything changed');
|
|
194
|
+
return [{ type: 'changed', file: '*', module: ctx.mainModule } as const];
|
|
195
|
+
} else {
|
|
196
|
+
return ManifestDeltaUtil.produceDelta(ctx, manifest);
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (changes) {
|
|
201
|
+
await LogUtil.withLogger('reset', async log => {
|
|
202
|
+
await fs.rm(path.resolve(ctx.workspacePath, ctx.outputFolder), { recursive: true, force: true });
|
|
203
|
+
log('info', 'Clearing output due to compiler changes');
|
|
204
|
+
}, false);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Write manifest
|
|
208
|
+
await LogUtil.withLogger('manifest', async log => {
|
|
209
|
+
await ManifestUtil.writeManifest(ctx, manifest);
|
|
210
|
+
log('debug', `Wrote manifest ${ctx.mainModule}`);
|
|
211
|
+
|
|
212
|
+
// Update all manifests
|
|
213
|
+
if (delta.length && ctx.monoRepo && !ctx.mainFolder) {
|
|
214
|
+
const names: string[] = [];
|
|
215
|
+
const mods = Object.values(manifest.modules).filter(x => x.local && x.name !== ctx.mainModule);
|
|
216
|
+
for (const mod of mods) {
|
|
217
|
+
await ManifestUtil.rewriteManifest(path.resolve(ctx.workspacePath, mod.sourceFolder));
|
|
218
|
+
names.push(mod.name);
|
|
219
|
+
}
|
|
220
|
+
log('debug', `Changes triggered ${delta.slice(0, 10).map(x => `${x.type}:${x.module}:${x.file}`)}`);
|
|
221
|
+
log('debug', `Rewrote monorepo manifests [changes=${delta.length}] ${names.slice(0, 10).join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const changed = delta.filter(x => x.type === 'added' || x.type === 'changed');
|
|
226
|
+
|
|
227
|
+
return { manifest, changed };
|
|
228
|
+
}
|
|
229
|
+
}
|
package/support/types.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type CompilerMode = 'build' | 'watch';
|
|
2
|
+
export type CompilerOp = CompilerMode | 'run';
|
|
3
|
+
|
|
4
|
+
export type CompilerStateType = 'startup' | 'init' | 'compile-start' | 'compile-end' | 'watch-start' | 'watch-end' | 'reset' | 'close';
|
|
5
|
+
export type CompilerChangeEvent = { file: string, action: 'create' | 'update' | 'delete', folder: string, output: string, module: string, time: number };
|
|
6
|
+
export type CompilerLogLevel = 'info' | 'debug' | 'warn' | 'error';
|
|
7
|
+
export type CompilerLogEvent = { level: CompilerLogLevel, message: string, time: number, args?: unknown[], scope?: string };
|
|
8
|
+
export type CompilerProgressEvent = { idx: number, total: number, message: string, operation: 'compile', complete?: boolean };
|
|
9
|
+
export type CompilerStateEvent = { state: CompilerStateType, extra?: Record<string, unknown> };
|
|
10
|
+
|
|
11
|
+
export type CompilerServerEvent =
|
|
12
|
+
{ type: 'change', payload: CompilerChangeEvent } |
|
|
13
|
+
{ type: 'log', payload: CompilerLogEvent } |
|
|
14
|
+
{ type: 'progress', payload: CompilerProgressEvent } |
|
|
15
|
+
{ type: 'state', payload: CompilerStateEvent };
|
|
16
|
+
|
|
17
|
+
export type CompilerServerEventType = CompilerServerEvent['type'];
|
|
18
|
+
|
|
19
|
+
export type CompilerServerInfo = {
|
|
20
|
+
path: string;
|
|
21
|
+
serverPid: number;
|
|
22
|
+
compilerPid: number;
|
|
23
|
+
state: CompilerStateType;
|
|
24
|
+
mode: CompilerMode,
|
|
25
|
+
iteration: number;
|
|
26
|
+
url: string;
|
|
27
|
+
};
|
package/support/util.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { createRequire } from 'module';
|
|
4
|
+
|
|
5
|
+
import type { ManifestContext } from '@travetto/manifest';
|
|
6
|
+
|
|
7
|
+
import { LogUtil } from './log';
|
|
8
|
+
|
|
9
|
+
const OPT_CACHE: Record<string, import('typescript').CompilerOptions> = {};
|
|
10
|
+
const SRC_REQ = createRequire(path.resolve('node_modules'));
|
|
11
|
+
|
|
12
|
+
export class CommonUtil {
|
|
13
|
+
/**
|
|
14
|
+
* Returns the compiler options
|
|
15
|
+
*/
|
|
16
|
+
static async getCompilerOptions(ctx: ManifestContext): Promise<{}> {
|
|
17
|
+
if (!(ctx.workspacePath in OPT_CACHE)) {
|
|
18
|
+
let tsconfig = path.resolve(ctx.workspacePath, 'tsconfig.json');
|
|
19
|
+
|
|
20
|
+
if (!await fs.stat(tsconfig).then(_ => true, _ => false)) {
|
|
21
|
+
tsconfig = SRC_REQ.resolve('@travetto/compiler/tsconfig.trv.json');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ts = (await import('typescript')).default;
|
|
25
|
+
|
|
26
|
+
const { options } = ts.parseJsonSourceFileConfigFileContent(
|
|
27
|
+
ts.readJsonConfigFile(tsconfig, ts.sys.readFile), ts.sys, ctx.workspacePath
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
OPT_CACHE[ctx.workspacePath] = {
|
|
31
|
+
...options,
|
|
32
|
+
allowJs: true,
|
|
33
|
+
resolveJsonModule: true,
|
|
34
|
+
sourceRoot: ctx.workspacePath,
|
|
35
|
+
rootDir: ctx.workspacePath,
|
|
36
|
+
outDir: path.resolve(ctx.workspacePath),
|
|
37
|
+
module: ctx.moduleType === 'commonjs' ? ts.ModuleKind.CommonJS : ts.ModuleKind.ESNext,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return OPT_CACHE[ctx.workspacePath];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Resolve module location
|
|
45
|
+
*/
|
|
46
|
+
static resolveModuleFolder(mod: string): string {
|
|
47
|
+
return path.dirname(SRC_REQ.resolve(`${mod}/package.json`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Determine file type
|
|
52
|
+
*/
|
|
53
|
+
static getFileType(file: string): 'ts' | 'js' | 'package-json' | 'typings' | undefined {
|
|
54
|
+
return file.endsWith('package.json') ? 'package-json' :
|
|
55
|
+
(file.endsWith('.js') ? 'js' :
|
|
56
|
+
(file.endsWith('.d.ts') ? 'typings' : (/[.]tsx?$/.test(file) ? 'ts' : undefined)));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Write text file, and ensure folder exists
|
|
61
|
+
*/
|
|
62
|
+
static writeTextFile = (file: string, content: string): Promise<void> =>
|
|
63
|
+
fs.mkdir(path.dirname(file), { recursive: true }).then(() => fs.writeFile(file, content, 'utf8'));
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Restartable Event Stream
|
|
67
|
+
*/
|
|
68
|
+
static async * restartableEvents<T>(src: (signal: AbortSignal) => AsyncIterable<T>, parent: AbortSignal, shouldRestart: (item: T) => boolean): AsyncIterable<T> {
|
|
69
|
+
outer: while (!parent.aborted) {
|
|
70
|
+
const controller = new AbortController();
|
|
71
|
+
// Chain
|
|
72
|
+
parent.addEventListener('abort', () => controller.abort());
|
|
73
|
+
|
|
74
|
+
const comp = src(controller.signal);
|
|
75
|
+
|
|
76
|
+
LogUtil.log('event-stream', 'debug', 'Started event stream');
|
|
77
|
+
|
|
78
|
+
// Wait for all events, close at the end
|
|
79
|
+
for await (const ev of comp) {
|
|
80
|
+
yield ev;
|
|
81
|
+
if (shouldRestart(ev)) {
|
|
82
|
+
controller.abort(); // Ensure terminated of process
|
|
83
|
+
continue outer;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
LogUtil.log('event-stream', 'debug', 'Finished event stream');
|
|
88
|
+
|
|
89
|
+
// Natural exit, we done
|
|
90
|
+
if (!controller.signal.aborted) { // Shutdown source if still running
|
|
91
|
+
controller.abort();
|
|
92
|
+
}
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Create a module loader given a context, and assuming build is complete
|
|
99
|
+
* @param ctx
|
|
100
|
+
*/
|
|
101
|
+
static moduleLoader(ctx: ManifestContext): (mod: string) => Promise<unknown> {
|
|
102
|
+
return (mod) => {
|
|
103
|
+
const outputRoot = path.resolve(ctx.workspacePath, ctx.outputFolder);
|
|
104
|
+
process.env.TRV_MANIFEST = path.resolve(outputRoot, 'node_modules', ctx.mainModule); // Setup for running
|
|
105
|
+
return import(path.join(outputRoot, 'node_modules', mod)); // Return function to run import on a module
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|