@travetto/compiler 7.0.3 → 7.0.4
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 +17 -30
- package/__index__.ts +8 -1
- package/bin/hook.js +23 -0
- package/bin/trvc-target.js +4 -0
- package/bin/trvc.js +4 -40
- package/package.json +5 -5
- package/{support/util.ts → src/common.ts} +11 -36
- package/src/compiler.ts +27 -18
- package/src/event.ts +10 -1
- package/{support → src}/log.ts +1 -1
- package/{support → src}/server/client.ts +15 -4
- package/src/server/manager.ts +90 -0
- package/{support → src}/server/process-handle.ts +2 -2
- package/{support → src}/server/server.ts +11 -13
- package/src/state.ts +60 -30
- package/src/ts-proxy.ts +11 -0
- package/src/types.ts +34 -2
- package/src/util.ts +18 -8
- package/src/watch.ts +56 -74
- package/support/invoke.ts +96 -0
- package/bin/entry.common.js +0 -88
- package/bin/gen.context.js +0 -73
- package/support/entry.compiler.ts +0 -3
- package/support/entry.main.ts +0 -118
- package/support/server/runner.ts +0 -80
- package/support/setup.ts +0 -246
- package/support/ts-util.ts +0 -42
- package/support/types.ts +0 -37
- /package/{support → src}/queue.ts +0 -0
package/src/state.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import
|
|
1
|
+
import type { CompilerHost, SourceFile, CompilerOptions, Program, ScriptTarget } from 'typescript';
|
|
2
2
|
|
|
3
|
-
import { path, ManifestModuleUtil, type ManifestModule, type ManifestRoot, ManifestIndex, ManifestModuleFolderType } from '@travetto/manifest';
|
|
4
|
-
import { TransformerManager } from '@travetto/transformer';
|
|
5
|
-
|
|
6
|
-
import { TypescriptUtil } from '../support/ts-util.ts';
|
|
3
|
+
import { path, ManifestModuleUtil, type ManifestModule, type ManifestRoot, type ManifestIndex, type ManifestModuleFolderType } from '@travetto/manifest';
|
|
4
|
+
import type { TransformerManager } from '@travetto/transformer';
|
|
7
5
|
|
|
8
6
|
import { CompilerUtil } from './util.ts';
|
|
9
|
-
import { CompileEmitError, CompileStateEntry } from './types.ts';
|
|
10
|
-
import { CommonUtil } from '
|
|
7
|
+
import type { CompileEmitError, CompileStateEntry } from './types.ts';
|
|
8
|
+
import { CommonUtil } from './common.ts';
|
|
9
|
+
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
|
-
export class CompilerState implements
|
|
14
|
+
export class CompilerState implements CompilerHost {
|
|
15
15
|
|
|
16
16
|
static async get(idx: ManifestIndex): Promise<CompilerState> {
|
|
17
17
|
return new CompilerState().init(idx);
|
|
@@ -29,15 +29,15 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
29
29
|
#tscOutputFileToOuptut = new Map<string, string>();
|
|
30
30
|
|
|
31
31
|
#sourceContents = new Map<string, string | undefined>();
|
|
32
|
-
#sourceFileObjects = new Map<string,
|
|
32
|
+
#sourceFileObjects = new Map<string, SourceFile>();
|
|
33
33
|
#sourceHashes = new Map<string, number>();
|
|
34
34
|
|
|
35
35
|
#manifestIndex: ManifestIndex;
|
|
36
36
|
#manifest: ManifestRoot;
|
|
37
37
|
#modules: ManifestModule[];
|
|
38
38
|
#transformerManager: TransformerManager;
|
|
39
|
-
#compilerOptions:
|
|
40
|
-
#program:
|
|
39
|
+
#compilerOptions: CompilerOptions;
|
|
40
|
+
#program: Program;
|
|
41
41
|
|
|
42
42
|
#readFile(sourceFile: string): string | undefined {
|
|
43
43
|
return ts.sys.readFile(this.#sourceToEntry.get(sourceFile)?.sourceFile ?? sourceFile);
|
|
@@ -57,44 +57,69 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
async #initCompilerOptions(): Promise<CompilerOptions> {
|
|
61
|
+
const tsconfigFile = CommonUtil.resolveWorkspace(this.#manifest, 'tsconfig.json');
|
|
62
|
+
if (!ts.sys.fileExists(tsconfigFile)) {
|
|
63
|
+
ts.sys.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }, null, 2));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const { options } = ts.parseJsonSourceFileConfigFileContent(
|
|
67
|
+
ts.readJsonConfigFile(tsconfigFile, ts.sys.readFile),
|
|
68
|
+
ts.sys,
|
|
69
|
+
this.#manifest.workspace.path
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
...options,
|
|
74
|
+
noEmit: false,
|
|
75
|
+
emitDeclarationOnly: false,
|
|
76
|
+
allowJs: true,
|
|
77
|
+
resolveJsonModule: true,
|
|
78
|
+
sourceRoot: this.#manifest.workspace.path,
|
|
79
|
+
rootDir: this.#manifest.workspace.path,
|
|
80
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
81
|
+
module: ts.ModuleKind.ESNext,
|
|
82
|
+
outDir: this.#outputPath
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
60
86
|
async init(idx: ManifestIndex): Promise<this> {
|
|
61
87
|
this.#manifestIndex = idx;
|
|
62
88
|
this.#manifest = idx.manifest;
|
|
63
89
|
this.#outputPath = path.resolve(this.#manifest.workspace.path, this.#manifest.build.outputFolder);
|
|
64
90
|
this.#typingsPath = path.resolve(this.#manifest.workspace.path, this.#manifest.build.typesFolder);
|
|
65
91
|
|
|
66
|
-
this.#compilerOptions = {
|
|
67
|
-
...await TypescriptUtil.getCompilerOptions(this.#manifest),
|
|
68
|
-
rootDir: this.#manifest.workspace.path,
|
|
69
|
-
outDir: this.#outputPath
|
|
70
|
-
};
|
|
71
|
-
|
|
72
92
|
this.#modules = Object.values(this.#manifest.modules);
|
|
73
93
|
|
|
74
94
|
// Register all inputs
|
|
75
|
-
for (const
|
|
76
|
-
const base =
|
|
95
|
+
for (const module of this.#modules) {
|
|
96
|
+
const base = module?.files ?? {};
|
|
77
97
|
const files = [
|
|
78
98
|
...base.bin ?? [],
|
|
79
99
|
...base.src ?? [],
|
|
80
100
|
...base.support ?? [],
|
|
81
101
|
...base.doc ?? [],
|
|
82
102
|
...base.test ?? [],
|
|
103
|
+
...base.$transformer ?? [],
|
|
83
104
|
...base.$index ?? [],
|
|
84
105
|
...base.$package ?? []
|
|
85
106
|
];
|
|
86
107
|
for (const [file, type] of files) {
|
|
87
|
-
if (
|
|
88
|
-
this.registerInput(
|
|
108
|
+
if (ManifestModuleUtil.isSourceType(type)) {
|
|
109
|
+
this.registerInput(module, file);
|
|
89
110
|
}
|
|
90
111
|
}
|
|
91
112
|
}
|
|
92
|
-
|
|
93
|
-
this.#transformerManager = await TransformerManager.create(this.#manifestIndex);
|
|
94
|
-
|
|
95
113
|
return this;
|
|
96
114
|
}
|
|
97
115
|
|
|
116
|
+
async initializeTypescript(): Promise<void> {
|
|
117
|
+
await tsProxyInit();
|
|
118
|
+
this.#compilerOptions = await this.#initCompilerOptions();
|
|
119
|
+
const { TransformerManager } = await import('@travetto/transformer');
|
|
120
|
+
this.#transformerManager ??= await TransformerManager.create(this.#manifestIndex);
|
|
121
|
+
}
|
|
122
|
+
|
|
98
123
|
get manifest(): ManifestRoot {
|
|
99
124
|
return this.#manifest;
|
|
100
125
|
}
|
|
@@ -109,13 +134,13 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
109
134
|
|
|
110
135
|
getArbitraryInputFile(): string {
|
|
111
136
|
const randomSource = this.#manifestIndex.getWorkspaceModules()
|
|
112
|
-
.filter(
|
|
137
|
+
.filter(module => module.files.src?.length)[0]
|
|
113
138
|
.files.src[0].sourceFile;
|
|
114
139
|
|
|
115
140
|
return this.getBySource(randomSource)!.sourceFile;
|
|
116
141
|
}
|
|
117
142
|
|
|
118
|
-
async createProgram(force = false): Promise<
|
|
143
|
+
async createProgram(force = false): Promise<Program> {
|
|
119
144
|
if (force || !this.#program) {
|
|
120
145
|
this.#program = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram: this.#program });
|
|
121
146
|
this.#transformerManager.init(this.#program.getTypeChecker());
|
|
@@ -162,6 +187,11 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
162
187
|
return this.#sourceToEntry.get(sourceFile);
|
|
163
188
|
}
|
|
164
189
|
|
|
190
|
+
isCompilerFile(file: string): boolean {
|
|
191
|
+
const entry = this.getBySource(file);
|
|
192
|
+
return (entry?.moduleFile && ManifestModuleUtil.getFileRole(entry.moduleFile) === 'compile') || entry?.module.roles.includes('compile') || false;
|
|
193
|
+
}
|
|
194
|
+
|
|
165
195
|
registerInput(module: ManifestModule, moduleFile: string): CompileStateEntry {
|
|
166
196
|
const relativeSource = `${module.sourceFolder || '.'}/${moduleFile}`;
|
|
167
197
|
const relativeOutput = `${module.outputFolder}/${moduleFile}`;
|
|
@@ -172,7 +202,7 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
172
202
|
const tscOutputFile = path.resolve(this.#outputPath, ManifestModuleUtil.withOutputExtension(relativeSource));
|
|
173
203
|
const outputFile = path.resolve(this.#outputPath, ManifestModuleUtil.withOutputExtension(relativeOutput));
|
|
174
204
|
|
|
175
|
-
const entry: CompileStateEntry = { sourceFile, outputFile, module, tscOutputFile, import: `${module.name}/${moduleFile}
|
|
205
|
+
const entry: CompileStateEntry = { sourceFile, outputFile, module, tscOutputFile, import: `${module.name}/${moduleFile}`, moduleFile };
|
|
176
206
|
|
|
177
207
|
this.#outputToEntry.set(outputFile, entry);
|
|
178
208
|
this.#sourceFiles.add(sourceFile);
|
|
@@ -199,7 +229,7 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
199
229
|
if (!contents || (contents.length === 0 && prevHash)) {
|
|
200
230
|
return false; // Ignore empty file
|
|
201
231
|
}
|
|
202
|
-
const currentHash =
|
|
232
|
+
const currentHash = CompilerUtil.naiveHash(contents);
|
|
203
233
|
const changed = prevHash !== currentHash;
|
|
204
234
|
if (changed) {
|
|
205
235
|
this.#sourceHashes.set(sourceFile, currentHash);
|
|
@@ -235,7 +265,7 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
235
265
|
/* Start Compiler Host */
|
|
236
266
|
getCanonicalFileName(file: string): string { return file; }
|
|
237
267
|
getCurrentDirectory(): string { return this.#manifest.workspace.path; }
|
|
238
|
-
getDefaultLibFileName(options:
|
|
268
|
+
getDefaultLibFileName(options: CompilerOptions): string { return ts.getDefaultLibFileName(options); }
|
|
239
269
|
getNewLine(): string { return ts.sys.newLine; }
|
|
240
270
|
useCaseSensitiveFileNames(): boolean { return ts.sys.useCaseSensitiveFileNames; }
|
|
241
271
|
getDefaultLibLocation(): string { return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions)); }
|
|
@@ -275,7 +305,7 @@ export class CompilerState implements ts.CompilerHost {
|
|
|
275
305
|
return contents;
|
|
276
306
|
}
|
|
277
307
|
|
|
278
|
-
getSourceFile(sourceFile: string, language:
|
|
308
|
+
getSourceFile(sourceFile: string, language: ScriptTarget): SourceFile {
|
|
279
309
|
if (!this.#sourceFileObjects.has(sourceFile)) {
|
|
280
310
|
const content = this.readFile(sourceFile)!;
|
|
281
311
|
this.#sourceFileObjects.set(sourceFile, ts.createSourceFile(sourceFile, content ?? '', language));
|
package/src/ts-proxy.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
|
2
|
+
import type ts from 'typescript';
|
|
3
|
+
|
|
4
|
+
let state: typeof ts | undefined;
|
|
5
|
+
export const tsProxyInit = (): Promise<unknown> => import('typescript').then(module => { state = module.default; });
|
|
6
|
+
|
|
7
|
+
export const tsProxy = new Proxy({}!, {
|
|
8
|
+
get(_, prop: string): unknown {
|
|
9
|
+
return state![prop as keyof typeof ts];
|
|
10
|
+
}
|
|
11
|
+
}) as typeof ts;
|
package/src/types.ts
CHANGED
|
@@ -2,9 +2,41 @@ import type ts from 'typescript';
|
|
|
2
2
|
|
|
3
3
|
import type { ChangeEventType, ManifestModule } from '@travetto/manifest';
|
|
4
4
|
|
|
5
|
+
export type CompilerStateType = 'startup' | 'init' | 'compile-start' | 'compile-end' | 'watch-start' | 'watch-end' | 'reset' | 'closed';
|
|
6
|
+
export type CompilerLogLevel = 'info' | 'debug' | 'warn' | 'error';
|
|
7
|
+
|
|
5
8
|
export type CompileEmitError = Error | readonly ts.Diagnostic[];
|
|
6
9
|
export type CompileEmitter = (file: string, newProgram?: boolean) => Promise<CompileEmitError | undefined>;
|
|
7
10
|
export type CompileEmitEvent = { file: string, i: number, total: number, error?: CompileEmitError, duration: number };
|
|
8
|
-
export type CompileStateEntry = { sourceFile: string, tscOutputFile: string, outputFile?: string, module: ManifestModule, import: string };
|
|
9
|
-
export type CompilerWatchEvent = { action: ChangeEventType, file: string, entry: CompileStateEntry };
|
|
11
|
+
export type CompileStateEntry = { sourceFile: string, tscOutputFile: string, outputFile?: string, module: ManifestModule, import: string, moduleFile: string };
|
|
12
|
+
export type CompilerWatchEvent = { action: ChangeEventType, file: string, entry: CompileStateEntry, moduleFile: string };
|
|
13
|
+
|
|
14
|
+
export type CompilerChangeEvent = { file: string, action: ChangeEventType, output: string, module: string, import: string, time: number };
|
|
15
|
+
export type CompilerLogEvent = { level: CompilerLogLevel, message: string, time?: number, args?: unknown[], scope?: string };
|
|
16
|
+
export type CompilerProgressEvent = { idx: number, total: number, message: string, operation: 'compile', complete?: boolean };
|
|
17
|
+
export type CompilerStateEvent = { state: CompilerStateType, extra?: Record<string, unknown> };
|
|
18
|
+
export type FileChangeEvent = { files: { file: string, action: ChangeEventType }[], time: number };
|
|
19
|
+
|
|
20
|
+
export type CompilerEvent =
|
|
21
|
+
{ type: 'file', payload: FileChangeEvent } |
|
|
22
|
+
{ type: 'change', payload: CompilerChangeEvent } |
|
|
23
|
+
{ type: 'log', payload: CompilerLogEvent } |
|
|
24
|
+
{ type: 'progress', payload: CompilerProgressEvent } |
|
|
25
|
+
{ type: 'state', payload: CompilerStateEvent } |
|
|
26
|
+
{ type: 'all', payload: unknown };
|
|
27
|
+
|
|
28
|
+
export type CompilerEventType = CompilerEvent['type'];
|
|
29
|
+
export type CompilerEventPayload<V> = (CompilerEvent & { type: V })['payload'];
|
|
30
|
+
|
|
31
|
+
export type CompilerServerInfo = {
|
|
32
|
+
path: string;
|
|
33
|
+
serverProcessId: number;
|
|
34
|
+
compilerProcessId: number;
|
|
35
|
+
state: CompilerStateType;
|
|
36
|
+
watching: boolean;
|
|
37
|
+
iteration: number;
|
|
38
|
+
url: string;
|
|
39
|
+
env?: Record<string, string>;
|
|
40
|
+
};
|
|
41
|
+
|
|
10
42
|
export class CompilerReset extends Error { }
|
package/src/util.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ManifestModuleUtil, type ManifestRoot, type Package } from '@travetto/manifest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import type { CompileEmitError } from './types.ts';
|
|
4
|
+
import { tsProxy as ts } from './ts-proxy.ts';
|
|
4
5
|
|
|
5
6
|
const nativeCwd = process.cwd();
|
|
6
7
|
|
|
@@ -9,11 +10,6 @@ const nativeCwd = process.cwd();
|
|
|
9
10
|
*/
|
|
10
11
|
export class CompilerUtil {
|
|
11
12
|
|
|
12
|
-
/**
|
|
13
|
-
* Determine if this is a manifest file we care about
|
|
14
|
-
*/
|
|
15
|
-
static validFile = (type: ManifestModuleFileType): boolean => type === 'ts' || type === 'package-json' || type === 'js' || type === 'typings';
|
|
16
|
-
|
|
17
13
|
/**
|
|
18
14
|
* Rewrites the package.json to target output file names, and pins versions
|
|
19
15
|
* @param manifest
|
|
@@ -47,7 +43,7 @@ export class CompilerUtil {
|
|
|
47
43
|
* @param filename The name of the file
|
|
48
44
|
* @param diagnostics The diagnostic errors
|
|
49
45
|
*/
|
|
50
|
-
static buildTranspileError(filename: string, diagnostics:
|
|
46
|
+
static buildTranspileError(filename: string, diagnostics: CompileEmitError): Error {
|
|
51
47
|
if (diagnostics instanceof Error) {
|
|
52
48
|
return diagnostics;
|
|
53
49
|
}
|
|
@@ -67,4 +63,18 @@ export class CompilerUtil {
|
|
|
67
63
|
}
|
|
68
64
|
return new Error(`Transpiling ${filename.replace(nativeCwd, '.')} failed: \n${errors.join('\n')}`);
|
|
69
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Naive hashing
|
|
69
|
+
*/
|
|
70
|
+
static naiveHash(text: string): number {
|
|
71
|
+
let hash = 5381;
|
|
72
|
+
|
|
73
|
+
for (let i = 0; i < text.length; i++) {
|
|
74
|
+
// eslint-disable-next-line no-bitwise
|
|
75
|
+
hash = (hash * 33) ^ text.charCodeAt(i);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return Math.abs(hash);
|
|
79
|
+
}
|
|
70
80
|
}
|
package/src/watch.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import { watch } from 'node:fs';
|
|
3
3
|
|
|
4
|
-
import { ManifestFileUtil, ManifestModuleUtil,
|
|
4
|
+
import { ManifestFileUtil, ManifestModuleUtil, ManifestUtil, PackageUtil, path } from '@travetto/manifest';
|
|
5
5
|
|
|
6
6
|
import { CompilerReset, type CompilerWatchEvent, type CompileStateEntry } from './types.ts';
|
|
7
|
-
import { CompilerState } from './state.ts';
|
|
8
|
-
import { CompilerUtil } from './util.ts';
|
|
7
|
+
import type { CompilerState } from './state.ts';
|
|
9
8
|
|
|
10
|
-
import { AsyncQueue } from '
|
|
11
|
-
import { IpcLogger } from '
|
|
9
|
+
import { AsyncQueue } from './queue.ts';
|
|
10
|
+
import { IpcLogger } from './log.ts';
|
|
12
11
|
import { EventUtil } from './event.ts';
|
|
13
12
|
|
|
14
13
|
const log = new IpcLogger({ level: 'debug' });
|
|
@@ -17,7 +16,7 @@ type CompilerWatchEventCandidate = Omit<CompilerWatchEvent, 'entry'> & { entry?:
|
|
|
17
16
|
|
|
18
17
|
export class CompilerWatcher {
|
|
19
18
|
#state: CompilerState;
|
|
20
|
-
#cleanup: Partial<Record<'tool' | 'workspace' | 'canary', () => (void | Promise<void>)>> = {};
|
|
19
|
+
#cleanup: Partial<Record<'tool' | 'workspace' | 'canary' | 'git', () => (void | Promise<void>)>> = {};
|
|
21
20
|
#watchCanary: string = '.trv/canary.id';
|
|
22
21
|
#lastWorkspaceModified = Date.now();
|
|
23
22
|
#watchCanaryFrequency = 5;
|
|
@@ -28,7 +27,7 @@ export class CompilerWatcher {
|
|
|
28
27
|
this.#state = state;
|
|
29
28
|
this.#root = state.manifest.workspace.path;
|
|
30
29
|
this.#queue = new AsyncQueue(signal);
|
|
31
|
-
signal.addEventListener('abort', () => Object.values(this.#cleanup).forEach(fn => fn()));
|
|
30
|
+
signal.addEventListener('abort', () => Object.values(this.#cleanup).forEach(fn => fn?.()));
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
async #getWatchIgnores(): Promise<string[]> {
|
|
@@ -38,7 +37,7 @@ export class CompilerWatcher {
|
|
|
38
37
|
'**/node_modules',
|
|
39
38
|
'.*/**/node_modules'
|
|
40
39
|
];
|
|
41
|
-
const ignores = new Set(['node_modules', '.git']);
|
|
40
|
+
const ignores = new Set(['node_modules', '.git', this.#state.resolveOutputFile('.')]);
|
|
42
41
|
for (const item of patterns) {
|
|
43
42
|
if (item.includes('*')) {
|
|
44
43
|
for await (const sub of fs.glob(item, { cwd: this.#root })) {
|
|
@@ -59,15 +58,17 @@ export class CompilerWatcher {
|
|
|
59
58
|
|
|
60
59
|
#toCandidateEvent({ action, file }: Pick<CompilerWatchEvent, 'action' | 'file'>): CompilerWatchEventCandidate {
|
|
61
60
|
let entry = this.#state.getBySource(file);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
61
|
+
const module = entry?.module ?? this.#state.manifestIndex.findModuleForArbitraryFile(file);
|
|
62
|
+
|
|
63
|
+
if (module && action === 'create' && !entry) {
|
|
64
|
+
const moduleRoot = module.sourceFolder || this.#root;
|
|
65
|
+
const moduleFile = file.includes(`${moduleRoot}/`) ? file.split(`${moduleRoot}/`)[1] : file;
|
|
66
|
+
entry = this.#state.registerInput(module, moduleFile);
|
|
67
67
|
} else if (action === 'delete' && entry) {
|
|
68
68
|
this.#state.removeSource(entry.sourceFile); // Ensure we remove it
|
|
69
69
|
}
|
|
70
|
-
|
|
70
|
+
|
|
71
|
+
return { entry, file: entry?.sourceFile ?? file, action, moduleFile: entry?.moduleFile! };
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
#isValidFile(file: string): boolean {
|
|
@@ -88,65 +89,24 @@ export class CompilerWatcher {
|
|
|
88
89
|
const relativeFile = event.file.replace(`${this.#root}/`, '');
|
|
89
90
|
log.debug(`Skipping update, as contents unchanged ${relativeFile}`);
|
|
90
91
|
return false;
|
|
91
|
-
} else if (!
|
|
92
|
+
} else if (!ManifestModuleUtil.isSourceType(event.file)) {
|
|
92
93
|
return false;
|
|
93
94
|
}
|
|
94
95
|
return true;
|
|
95
96
|
}
|
|
96
97
|
|
|
97
|
-
#
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const mod = event.entry.module;
|
|
105
|
-
const moduleSet = new Set(this.#state.manifestIndex.getDependentModules(mod.name, 'parents').map(indexedMod => indexedMod.name));
|
|
106
|
-
moduleSet.add(this.#state.manifest.workspace.name);
|
|
107
|
-
for (const moduleName of moduleSet) {
|
|
108
|
-
if (!eventsByMod.has(moduleName)) {
|
|
109
|
-
eventsByMod.set(moduleName, []);
|
|
110
|
-
}
|
|
111
|
-
eventsByMod.get(moduleName)!.push(event);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return eventsByMod;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
#updateManifestForEvent({ action, file, entry }: CompilerWatchEvent, manifest: ManifestRoot): void {
|
|
118
|
-
if (action === 'update') {
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const moduleName = entry.module.name;
|
|
123
|
-
const moduleRoot = entry.module.sourceFolder || this.#root;
|
|
124
|
-
const relativeFile = file.includes(moduleRoot) ? file.split(`${moduleRoot}/`)[1] : file;
|
|
125
|
-
const folderKey = ManifestModuleUtil.getFolderKey(relativeFile);
|
|
126
|
-
const fileType = ManifestModuleUtil.getFileType(relativeFile);
|
|
127
|
-
const roleType = ManifestModuleUtil.getFileRole(relativeFile)!;
|
|
128
|
-
|
|
129
|
-
const manifestModuleFiles = manifest.modules[moduleName].files[folderKey] ??= [];
|
|
130
|
-
const idx = manifestModuleFiles.findIndex(indexedFile => indexedFile[0] === relativeFile);
|
|
131
|
-
const wrappedIdx = idx < 0 ? manifestModuleFiles.length : idx;
|
|
132
|
-
|
|
133
|
-
switch (action) {
|
|
134
|
-
case 'create': manifestModuleFiles[wrappedIdx] = [relativeFile, fileType, Date.now(), roleType]; break;
|
|
135
|
-
case 'delete': idx >= 0 && manifestModuleFiles.splice(idx, 1); break;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
async #reconcileManifestUpdates(compilerEvents: CompilerWatchEvent[]): Promise<void> {
|
|
140
|
-
for (const [mod, events] of this.#getManifestUpdateEventsByParents(compilerEvents).entries()) {
|
|
141
|
-
const moduleRoot = this.#state.manifestIndex.getManifestModule(mod)!.sourceFolder;
|
|
142
|
-
const moduleContext = ManifestUtil.getModuleContext(this.#state.manifest, moduleRoot);
|
|
143
|
-
const manifestLocation = ManifestUtil.getManifestLocation(moduleContext, mod);
|
|
144
|
-
const moduleManifest = ManifestUtil.readManifestSync(manifestLocation);
|
|
98
|
+
async #updateManifestWithEvents(compilerEvents: CompilerWatchEvent[]): Promise<void> {
|
|
99
|
+
const eventsByModule = this.#state.manifestIndex.groupByLineage(
|
|
100
|
+
compilerEvents.map(event => ({ item: event, module: event.entry!.module.name }))
|
|
101
|
+
.filter(x => x.item.action !== 'update')
|
|
102
|
+
);
|
|
145
103
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
104
|
+
for (const [moduleName, events] of eventsByModule.entries()) {
|
|
105
|
+
const moduleManifest = this.#state.manifestIndex.resolveDependentManifest(moduleName);
|
|
106
|
+
for (const { moduleFile, action, entry } of events) {
|
|
107
|
+
ManifestUtil.updateManifest(moduleManifest, entry.module.name, moduleFile, action);
|
|
149
108
|
}
|
|
109
|
+
log.debug('Updating manifest', { module: moduleName, events: events.length });
|
|
150
110
|
await ManifestUtil.writeManifest(moduleManifest);
|
|
151
111
|
}
|
|
152
112
|
|
|
@@ -176,11 +136,18 @@ export class CompilerWatcher {
|
|
|
176
136
|
}
|
|
177
137
|
|
|
178
138
|
// One event per file set
|
|
179
|
-
const filesChanged = events
|
|
139
|
+
const filesChanged = events
|
|
140
|
+
.map(event => ({ file: path.toPosix(event.path), action: event.type }))
|
|
141
|
+
.filter(event => this.#isValidFile(event.file));
|
|
142
|
+
|
|
180
143
|
if (filesChanged.length) {
|
|
181
144
|
EventUtil.sendEvent('file', { time: Date.now(), files: filesChanged });
|
|
182
145
|
}
|
|
183
146
|
|
|
147
|
+
if (filesChanged.some(item => this.#state.isCompilerFile(item.file))) {
|
|
148
|
+
throw new CompilerReset('Compiler has changed, restarting');
|
|
149
|
+
}
|
|
150
|
+
|
|
184
151
|
const items = filesChanged
|
|
185
152
|
.map(event => this.#toCandidateEvent(event))
|
|
186
153
|
.filter(event => this.#isValidEvent(event));
|
|
@@ -190,10 +157,10 @@ export class CompilerWatcher {
|
|
|
190
157
|
}
|
|
191
158
|
|
|
192
159
|
try {
|
|
193
|
-
await this.#
|
|
160
|
+
await this.#updateManifestWithEvents(items);
|
|
194
161
|
} catch (manifestError) {
|
|
195
162
|
log.info('Restarting due to manifest rebuild failure', manifestError);
|
|
196
|
-
throw new CompilerReset(`Manifest rebuild failure: ${manifestError}`);
|
|
163
|
+
throw new CompilerReset(`Manifest rebuild failure: ${manifestError} `);
|
|
197
164
|
}
|
|
198
165
|
|
|
199
166
|
for (const item of items) {
|
|
@@ -203,7 +170,7 @@ export class CompilerWatcher {
|
|
|
203
170
|
if (out instanceof Error && out.message.includes('Events were dropped by the FSEvents client.')) {
|
|
204
171
|
out = new CompilerReset('FSEvents failure, requires restart');
|
|
205
172
|
}
|
|
206
|
-
return this.#queue.throw(out instanceof Error ? out : new Error(`${out}`));
|
|
173
|
+
return this.#queue.throw(out instanceof Error ? out : new Error(`${out} `));
|
|
207
174
|
}
|
|
208
175
|
}, { ignore });
|
|
209
176
|
|
|
@@ -212,10 +179,9 @@ export class CompilerWatcher {
|
|
|
212
179
|
|
|
213
180
|
async #listenToolFolder(): Promise<void> {
|
|
214
181
|
const build = this.#state.manifest.build;
|
|
215
|
-
const toolRootFolder = path.dirname(path.resolve(this.#root, build.
|
|
216
|
-
const toolFolders = new Set([
|
|
217
|
-
|
|
218
|
-
].map(folder => path.resolve(this.#root, folder)));
|
|
182
|
+
const toolRootFolder = path.dirname(path.resolve(this.#root, build.outputFolder));
|
|
183
|
+
const toolFolders = new Set([toolRootFolder, build.typesFolder, build.outputFolder]
|
|
184
|
+
.map(folder => path.resolve(this.#root, folder)));
|
|
219
185
|
|
|
220
186
|
log.debug('Tooling Folders', [...toolFolders].map(folder => folder.replace(`${this.#root}/`, '')));
|
|
221
187
|
|
|
@@ -258,11 +224,27 @@ export class CompilerWatcher {
|
|
|
258
224
|
this.#cleanup.canary = (): void => clearInterval(canaryId);
|
|
259
225
|
}
|
|
260
226
|
|
|
227
|
+
async #listenGitChanges(): Promise<void> {
|
|
228
|
+
const gitFolder = path.resolve(this.#root, '.git');
|
|
229
|
+
if (!await fs.stat(gitFolder).catch(() => false)) { return; }
|
|
230
|
+
log.debug('Starting git canary');
|
|
231
|
+
const listener = watch(gitFolder, { encoding: 'utf8' }, async (event, file) => {
|
|
232
|
+
if (!file) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
if (file === 'HEAD') {
|
|
236
|
+
this.#queue.throw(new CompilerReset('Git branch change detected'));
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
this.#cleanup.git = (): void => listener.close();
|
|
240
|
+
}
|
|
241
|
+
|
|
261
242
|
[Symbol.asyncIterator](): AsyncIterator<CompilerWatchEvent> {
|
|
262
243
|
if (!this.#cleanup.workspace) {
|
|
263
244
|
this.#listenWorkspace();
|
|
264
245
|
this.#listenToolFolder();
|
|
265
246
|
this.#listenCanary();
|
|
247
|
+
this.#listenGitChanges();
|
|
266
248
|
}
|
|
267
249
|
return this.#queue[Symbol.asyncIterator]();
|
|
268
250
|
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
// @trv-no-transform
|
|
2
|
+
import { getManifestContext, ManifestUtil } from '@travetto/manifest';
|
|
3
|
+
|
|
4
|
+
import { Log } from '../src/log.ts';
|
|
5
|
+
import { CompilerManager } from '../src/server/manager.ts';
|
|
6
|
+
import { CompilerClient } from '../src/server/client.ts';
|
|
7
|
+
import { CommonUtil } from '../src/common.ts';
|
|
8
|
+
import { EventUtil } from '../src/event.ts';
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
npx trvc [command]
|
|
12
|
+
|
|
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 --prod [output] - Generate the project manifest
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Invoke the compiler
|
|
27
|
+
*/
|
|
28
|
+
export async function invoke(operation?: string, args: string[] = []): Promise<unknown> {
|
|
29
|
+
if (operation === undefined) {
|
|
30
|
+
[operation, ...args] = process.argv.slice(2);
|
|
31
|
+
}
|
|
32
|
+
const ctx = getManifestContext();
|
|
33
|
+
const client = new CompilerClient(ctx, Log.scoped('client'));
|
|
34
|
+
|
|
35
|
+
Log.initLevel('error');
|
|
36
|
+
Log.root = ctx.workspace.path;
|
|
37
|
+
|
|
38
|
+
switch (operation) {
|
|
39
|
+
case undefined:
|
|
40
|
+
case 'help': console.log(HELP); break;
|
|
41
|
+
case 'start':
|
|
42
|
+
case 'watch': return CompilerManager.compile(ctx, client, { watch: true });
|
|
43
|
+
case 'build': return CompilerManager.compile(ctx, client, { watch: false });
|
|
44
|
+
case 'restart': return CompilerManager.compile(ctx, client, { watch: true, forceRestart: true });
|
|
45
|
+
case 'info': {
|
|
46
|
+
const info = await client.info();
|
|
47
|
+
return CommonUtil.writeStdout(2, info);
|
|
48
|
+
}
|
|
49
|
+
case 'event': {
|
|
50
|
+
if (!EventUtil.isComplilerEventType(args[0])) {
|
|
51
|
+
throw new Error(`Unknown event type: ${args[0]}`);
|
|
52
|
+
}
|
|
53
|
+
for await (const event of client.fetchEvents(args[0])) {
|
|
54
|
+
await CommonUtil.writeStdout(0, event);
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
case 'manifest:production':
|
|
59
|
+
case 'manifest': {
|
|
60
|
+
let manifest = await ManifestUtil.buildManifest(ctx);
|
|
61
|
+
if (operation === 'manifest:production') {
|
|
62
|
+
manifest = await ManifestUtil.createProductionManifest(manifest);
|
|
63
|
+
}
|
|
64
|
+
if (args[0]) {
|
|
65
|
+
await ManifestUtil.writeManifestToFile(args[0], manifest);
|
|
66
|
+
console.log(`Wrote manifest to ${args[0]}`);
|
|
67
|
+
} else {
|
|
68
|
+
await CommonUtil.writeStdout(2, manifest);
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
case 'clean': {
|
|
73
|
+
await client.clean(true);
|
|
74
|
+
console.log(`Clean triggered ${ctx.workspace.path}`);
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
case 'stop': {
|
|
78
|
+
if (await client.stop()) {
|
|
79
|
+
console.log(`Stopped server ${ctx.workspace.path}: ${client.url}`);
|
|
80
|
+
} else {
|
|
81
|
+
console.log(`Server not running ${ctx.workspace.path}: ${client.url}`);
|
|
82
|
+
}
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'exec': {
|
|
86
|
+
await CompilerManager.compileIfNecessary(ctx, client);
|
|
87
|
+
Log.initLevel('none');
|
|
88
|
+
process.env.TRV_MANIFEST = CommonUtil.resolveWorkspace(ctx, ctx.build.outputFolder, 'node_modules', ctx.main.name); // Setup for running
|
|
89
|
+
const importTarget = CommonUtil.resolveWorkspace(ctx, ctx.build.outputFolder, 'node_modules', args[0]);
|
|
90
|
+
process.argv = [process.argv0, importTarget, ...args.slice(1)];
|
|
91
|
+
// Return function to run import on a module
|
|
92
|
+
return import(importTarget);
|
|
93
|
+
}
|
|
94
|
+
default: console.error(`\nUnknown trvc operation: ${operation}\n${HELP}`);
|
|
95
|
+
}
|
|
96
|
+
}
|