@travetto/compiler 3.0.0-rc.14 → 3.0.0-rc.16
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/__index__.ts +4 -1
- package/bin/trv.js +15 -11
- package/package.json +5 -5
- package/src/compiler.ts +43 -82
- package/src/log.ts +18 -0
- package/src/state.ts +146 -144
- package/src/types.ts +14 -0
- package/src/util.ts +16 -12
- package/src/watch.ts +142 -0
- package/support/launcher.ts +45 -65
- package/support/lock-pinger.ts +25 -0
- package/support/lock.ts +237 -0
- package/support/log.ts +31 -0
- package/support/transpile.ts +63 -37
package/src/state.ts
CHANGED
|
@@ -1,35 +1,47 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
|
-
import
|
|
2
|
+
import os from 'os';
|
|
3
3
|
|
|
4
|
-
import { path, ManifestModuleUtil, ManifestModule,
|
|
4
|
+
import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex } from '@travetto/manifest';
|
|
5
|
+
import { TransformerManager } from '@travetto/transformer';
|
|
5
6
|
|
|
6
7
|
import { CompilerUtil } from './util';
|
|
7
8
|
import { TranspileUtil } from '../support/transpile';
|
|
9
|
+
import { CompileStateEntry } from './types';
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
export class CompilerState implements ts.CompilerHost {
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
static async get(idx: ManifestIndex): Promise<CompilerState> {
|
|
14
|
+
return new CompilerState().init(idx);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private constructor() { }
|
|
12
18
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#inputToOutput = new Map<string, string | undefined>();
|
|
19
|
+
#rootDir = path.resolve(os.tmpdir(), '_');
|
|
20
|
+
#outputPath: string;
|
|
21
|
+
#inputFiles = new Set<string>();
|
|
17
22
|
#inputDirectoryToSource = new Map<string, string>();
|
|
18
|
-
#
|
|
23
|
+
#inputToEntry = new Map<string, CompileStateEntry>();
|
|
24
|
+
#sourceToEntry = new Map<string, CompileStateEntry>();
|
|
25
|
+
#outputToEntry = new Map<string, CompileStateEntry>();
|
|
19
26
|
|
|
20
27
|
#sourceContents = new Map<string, string | undefined>();
|
|
21
28
|
#sourceFileObjects = new Map<string, ts.SourceFile>();
|
|
22
|
-
#sourceHashes = new Map<string, number>();
|
|
23
29
|
|
|
30
|
+
#manifestIndex: ManifestIndex;
|
|
24
31
|
#manifest: ManifestRoot;
|
|
25
32
|
#modules: ManifestModule[];
|
|
26
|
-
#
|
|
33
|
+
#transformerManager: TransformerManager;
|
|
34
|
+
#compilerOptions: ts.CompilerOptions;
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
this.#
|
|
36
|
+
async init(idx: ManifestIndex): Promise<this> {
|
|
37
|
+
this.#manifestIndex = idx;
|
|
38
|
+
this.#manifest = idx.manifest;
|
|
39
|
+
this.#outputPath = path.resolve(this.#manifest.workspacePath, this.#manifest.outputFolder);
|
|
30
40
|
this.#modules = Object.values(this.#manifest.modules);
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
|
|
42
|
+
// Register all inputs
|
|
43
|
+
for (const x of this.#modules) {
|
|
44
|
+
const files = [
|
|
33
45
|
...x.files.bin ?? [],
|
|
34
46
|
...x.files.src ?? [],
|
|
35
47
|
...x.files.support ?? [],
|
|
@@ -37,67 +49,98 @@ export class CompilerState {
|
|
|
37
49
|
...x.files.test ?? [],
|
|
38
50
|
...x.files.$index ?? [],
|
|
39
51
|
...x.files.$package ?? []
|
|
40
|
-
]
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
);
|
|
50
|
-
}
|
|
52
|
+
];
|
|
53
|
+
for (const [file, type] of files) {
|
|
54
|
+
if (CompilerUtil.validFile(type) || type === 'typings') {
|
|
55
|
+
this.registerInput(x, file);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.#transformerManager = await TransformerManager.create(this.#manifestIndex);
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
return {
|
|
62
|
+
this.#compilerOptions = {
|
|
54
63
|
...await TranspileUtil.getCompilerOptions(this.#manifest),
|
|
55
|
-
|
|
64
|
+
rootDir: this.#rootDir,
|
|
65
|
+
outDir: this.#outputPath
|
|
56
66
|
};
|
|
67
|
+
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get manifest(): ManifestRoot {
|
|
72
|
+
return this.#manifest;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get manifestIndex(): ManifestIndex {
|
|
76
|
+
return this.#manifestIndex;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
resolveOutputFile(file: string): string {
|
|
80
|
+
return path.resolve(this.#manifest.workspacePath, this.#manifest.outputFolder, file);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
getArbitraryInputFile(): string {
|
|
84
|
+
return this.getBySource(this.#manifestIndex.getModule('@travetto/manifest')!.files.src[0].sourceFile)!.input;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
createProgram(oldProgram?: ts.Program): ts.Program {
|
|
88
|
+
const prog = ts.createProgram({ rootNames: this.getAllFiles(), host: this, options: this.#compilerOptions, oldProgram });
|
|
89
|
+
this.#transformerManager.init(prog.getTypeChecker());
|
|
90
|
+
return prog;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
writeInputFile(program: ts.Program, inputFile: string): ts.EmitResult | undefined {
|
|
94
|
+
if (inputFile.endsWith('.json')) {
|
|
95
|
+
this.writeFile(this.#inputToEntry.get(inputFile)!.output!, this.readFile(inputFile)!, false);
|
|
96
|
+
} else if (inputFile.endsWith('.js')) {
|
|
97
|
+
this.writeFile(this.#inputToEntry.get(inputFile)!.output!, ts.transpile(this.readFile(inputFile)!, this.#compilerOptions), false);
|
|
98
|
+
} else if (inputFile.endsWith('.ts')) {
|
|
99
|
+
return program.emit(
|
|
100
|
+
program.getSourceFile(inputFile)!,
|
|
101
|
+
(...args) => this.writeFile(...args), undefined, false,
|
|
102
|
+
this.#transformerManager.get()
|
|
103
|
+
);
|
|
104
|
+
}
|
|
57
105
|
}
|
|
58
106
|
|
|
59
|
-
|
|
60
|
-
return this.#
|
|
107
|
+
getBySource(sourceFile: string): CompileStateEntry | undefined {
|
|
108
|
+
return this.#sourceToEntry.get(sourceFile);
|
|
61
109
|
}
|
|
62
110
|
|
|
63
111
|
registerInput(module: ManifestModule, moduleFile: string): string {
|
|
64
112
|
const relativeInput = `${module.outputFolder}/${moduleFile}`;
|
|
65
113
|
const sourceFile = path.toPosix(path.resolve(this.#manifest.workspacePath, module.sourceFolder, moduleFile));
|
|
66
114
|
const sourceFolder = path.dirname(sourceFile);
|
|
67
|
-
const inputFile = path.resolve(this.#
|
|
115
|
+
const inputFile = path.resolve(this.#rootDir, relativeInput); // Ensure input is isolated
|
|
68
116
|
const inputFolder = path.dirname(inputFile);
|
|
69
117
|
const fileType = ManifestModuleUtil.getFileType(moduleFile);
|
|
70
118
|
const outputFile = fileType === 'typings' ?
|
|
71
119
|
undefined :
|
|
72
|
-
path.resolve(
|
|
73
|
-
this.#manifest.workspacePath,
|
|
74
|
-
this.#manifest.outputFolder,
|
|
75
|
-
CompilerUtil.inputToOutput(relativeInput)
|
|
76
|
-
);
|
|
120
|
+
path.resolve(this.#outputPath, CompilerUtil.inputToOutput(relativeInput));
|
|
77
121
|
|
|
78
|
-
|
|
79
|
-
const stagedOutputFile = CompilerUtil.inputToOutput(inputFile);
|
|
122
|
+
const entry = { source: sourceFile, input: inputFile, output: outputFile, module, relativeInput };
|
|
80
123
|
|
|
81
|
-
this.#
|
|
82
|
-
this.#
|
|
83
|
-
this.#inputToOutput.set(inputFile, outputFile);
|
|
124
|
+
this.#inputToEntry.set(inputFile, entry);
|
|
125
|
+
this.#sourceToEntry.set(sourceFile, entry);
|
|
84
126
|
this.#inputDirectoryToSource.set(inputFolder, sourceFolder);
|
|
85
127
|
|
|
86
|
-
if (
|
|
87
|
-
this.#
|
|
88
|
-
this.#stagedOutputToOutput.set(`${stagedOutputFile}.map`, `${outputFile!}.map`);
|
|
128
|
+
if (outputFile) {
|
|
129
|
+
this.#outputToEntry.set(outputFile, entry);
|
|
89
130
|
}
|
|
90
131
|
|
|
132
|
+
this.#inputFiles.add(inputFile);
|
|
133
|
+
|
|
91
134
|
return inputFile;
|
|
92
135
|
}
|
|
93
136
|
|
|
94
137
|
removeInput(inputFile: string): void {
|
|
95
|
-
const source = this.#
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
this.#
|
|
100
|
-
this.#
|
|
138
|
+
const { output, source } = this.#inputToEntry.get(inputFile)!;
|
|
139
|
+
if (output) {
|
|
140
|
+
this.#outputToEntry.delete(output);
|
|
141
|
+
}
|
|
142
|
+
this.#sourceToEntry.delete(source);
|
|
143
|
+
this.#inputToEntry.delete(inputFile);
|
|
101
144
|
this.#inputFiles.delete(inputFile);
|
|
102
145
|
}
|
|
103
146
|
|
|
@@ -106,106 +149,65 @@ export class CompilerState {
|
|
|
106
149
|
this.#sourceContents.delete(inputFile);
|
|
107
150
|
}
|
|
108
151
|
|
|
109
|
-
|
|
110
|
-
return this.#
|
|
152
|
+
getAllFiles(): string[] {
|
|
153
|
+
return [...this.#inputFiles];
|
|
111
154
|
}
|
|
112
155
|
|
|
113
|
-
|
|
114
|
-
|
|
156
|
+
/**
|
|
157
|
+
* Used for translating non-project files (e.g. dependencies/node_modules) to the faux-root
|
|
158
|
+
* that is used during compilation.
|
|
159
|
+
*/
|
|
160
|
+
translateRawInput(input: string): string {
|
|
161
|
+
return input.replace(this.#rootDir, this.#manifest.workspacePath);
|
|
115
162
|
}
|
|
116
163
|
|
|
117
|
-
|
|
118
|
-
|
|
164
|
+
/* Start Compiler Host */
|
|
165
|
+
getCanonicalFileName(file: string): string { return file; }
|
|
166
|
+
getCurrentDirectory(): string { return path.cwd(); }
|
|
167
|
+
getDefaultLibFileName(opts: ts.CompilerOptions): string { return ts.getDefaultLibFileName(opts); }
|
|
168
|
+
getNewLine(): string { return ts.sys.newLine; }
|
|
169
|
+
useCaseSensitiveFileNames(): boolean { return ts.sys.useCaseSensitiveFileNames; }
|
|
170
|
+
getDefaultLibLocation(): string { return path.dirname(ts.getDefaultLibFilePath(this.#compilerOptions)); }
|
|
171
|
+
|
|
172
|
+
fileExists(inputFile: string): boolean {
|
|
173
|
+
return this.#inputToEntry.has(inputFile) || ts.sys.fileExists(this.translateRawInput(inputFile));
|
|
119
174
|
}
|
|
120
175
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
create: (inputFile: string) => void;
|
|
124
|
-
update: (inputFile: string) => void;
|
|
125
|
-
delete: (outputFile: string) => void;
|
|
126
|
-
}): (ev: WatchEvent, folder: string) => void {
|
|
127
|
-
const mods = Object.fromEntries(this.modules.map(x => [path.resolve(this.#manifest.workspacePath, x.sourceFolder), x]));
|
|
128
|
-
return ({ file: sourceFile, action }: WatchEvent, folder: string): void => {
|
|
129
|
-
const mod = mods[folder];
|
|
130
|
-
const moduleFile = sourceFile.includes(mod.sourceFolder) ? sourceFile.split(`${mod.sourceFolder}/`)[1] : sourceFile;
|
|
131
|
-
switch (action) {
|
|
132
|
-
case 'create': {
|
|
133
|
-
const fileType = ManifestModuleUtil.getFileType(moduleFile);
|
|
134
|
-
if (validFile(fileType)) {
|
|
135
|
-
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
136
|
-
const input = this.registerInput(mod, moduleFile);
|
|
137
|
-
this.#sourceHashes.set(sourceFile, hash);
|
|
138
|
-
handler.create(input);
|
|
139
|
-
}
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
case 'update': {
|
|
143
|
-
const io = this.#sourceInputOutput.get(sourceFile);
|
|
144
|
-
if (io) {
|
|
145
|
-
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
146
|
-
if (this.#sourceHashes.get(sourceFile) !== hash) {
|
|
147
|
-
this.resetInputSource(io.input);
|
|
148
|
-
this.#sourceHashes.set(sourceFile, hash);
|
|
149
|
-
handler.update(io.input);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
break;
|
|
153
|
-
}
|
|
154
|
-
case 'delete': {
|
|
155
|
-
const io = this.#sourceInputOutput.get(sourceFile);
|
|
156
|
-
if (io) {
|
|
157
|
-
this.removeInput(io.input);
|
|
158
|
-
if (io.output) {
|
|
159
|
-
handler.delete(io.output);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|
|
176
|
+
directoryExists(inputDir: string): boolean {
|
|
177
|
+
return this.#inputDirectoryToSource.has(inputDir) || ts.sys.directoryExists(this.translateRawInput(inputDir));
|
|
165
178
|
}
|
|
166
179
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
ts.sys.writeFile(outputFile, text, bom);
|
|
200
|
-
},
|
|
201
|
-
getSourceFile: (inputFile: string, language: ts.ScriptTarget, __onErr?: unknown): ts.SourceFile => {
|
|
202
|
-
if (!this.#sourceFileObjects.has(inputFile)) {
|
|
203
|
-
const content = host.readFile(inputFile)!;
|
|
204
|
-
this.#sourceFileObjects.set(inputFile, ts.createSourceFile(inputFile, content ?? '', language));
|
|
205
|
-
}
|
|
206
|
-
return this.#sourceFileObjects.get(inputFile)!;
|
|
207
|
-
}
|
|
208
|
-
};
|
|
209
|
-
return host;
|
|
180
|
+
writeFile(
|
|
181
|
+
outputFile: string,
|
|
182
|
+
text: string,
|
|
183
|
+
bom: boolean,
|
|
184
|
+
onError?: (message: string) => void,
|
|
185
|
+
sourceFiles?: readonly ts.SourceFile[],
|
|
186
|
+
data?: ts.WriteFileCallbackData
|
|
187
|
+
): void {
|
|
188
|
+
if (outputFile.endsWith('package.json')) {
|
|
189
|
+
text = CompilerUtil.rewritePackageJSON(this.#manifest, text);
|
|
190
|
+
} else if (!this.#compilerOptions.inlineSourceMap && this.#compilerOptions.sourceMap && outputFile.endsWith('.map')) {
|
|
191
|
+
text = CompilerUtil.rewriteSourceMap(this.#manifest, text, f => this.#outputToEntry.get(f.replace(/[.]map$/, ''))!);
|
|
192
|
+
} else if (this.#compilerOptions.inlineSourceMap && CompilerUtil.isSourceMapUrlPosData(data)) {
|
|
193
|
+
text = CompilerUtil.rewriteInlineSourceMap(this.#manifest, text, f => this.#outputToEntry.get(f)!, data);
|
|
194
|
+
}
|
|
195
|
+
ts.sys.writeFile(outputFile, text, bom);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
readFile(inputFile: string): string | undefined {
|
|
199
|
+
const res = this.#sourceContents.get(inputFile) ?? ts.sys.readFile(
|
|
200
|
+
this.#inputToEntry.get(inputFile)?.source ?? this.translateRawInput(inputFile)
|
|
201
|
+
);
|
|
202
|
+
this.#sourceContents.set(inputFile, res);
|
|
203
|
+
return res;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
getSourceFile(inputFile: string, language: ts.ScriptTarget): ts.SourceFile {
|
|
207
|
+
if (!this.#sourceFileObjects.has(inputFile)) {
|
|
208
|
+
const content = this.readFile(inputFile)!;
|
|
209
|
+
this.#sourceFileObjects.set(inputFile, ts.createSourceFile(inputFile, content ?? '', language));
|
|
210
|
+
}
|
|
211
|
+
return this.#sourceFileObjects.get(inputFile)!;
|
|
210
212
|
}
|
|
211
213
|
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type ts from 'typescript';
|
|
2
|
+
|
|
3
|
+
import type { ManifestModule } from '@travetto/manifest';
|
|
4
|
+
|
|
5
|
+
export type CompileEmitError = Error | readonly ts.Diagnostic[];
|
|
6
|
+
export type CompileEmitter = (file: string, newProgram?: boolean) => Promise<CompileEmitError | undefined>;
|
|
7
|
+
export type CompileEmitEvent = { file: string, i: number, total: number, err?: CompileEmitError };
|
|
8
|
+
export type CompileStateEntry = { source: string, input: string, relativeInput: string, output?: string, module: ManifestModule };
|
|
9
|
+
|
|
10
|
+
export type CompileWatcherHandler = {
|
|
11
|
+
create: (inputFile: string) => void;
|
|
12
|
+
update: (inputFile: string) => void;
|
|
13
|
+
delete: (outputFile: string) => void;
|
|
14
|
+
};
|
package/src/util.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import ts from 'typescript';
|
|
2
2
|
|
|
3
|
-
import { ManifestRoot, Package, path } from '@travetto/manifest';
|
|
3
|
+
import { ManifestContext, ManifestModuleFileType, ManifestRoot, Package, path } from '@travetto/manifest';
|
|
4
4
|
|
|
5
|
-
type
|
|
5
|
+
type OutputToSource = (outputFile: string) => ({ source: string } | undefined);
|
|
6
6
|
export type FileWatchEvent = { type: 'create' | 'delete' | 'update', path: string };
|
|
7
7
|
|
|
8
8
|
const nativeCwd = process.cwd();
|
|
@@ -12,6 +12,11 @@ const nativeCwd = process.cwd();
|
|
|
12
12
|
*/
|
|
13
13
|
export class CompilerUtil {
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Determine if this is a manifest file we care about
|
|
17
|
+
*/
|
|
18
|
+
static validFile = (type: ManifestModuleFileType): boolean => type === 'ts' || type === 'package-json' || type === 'js';
|
|
19
|
+
|
|
15
20
|
/**
|
|
16
21
|
* Map input file to output format, generally converting ts extensions to js
|
|
17
22
|
* @param file
|
|
@@ -34,36 +39,35 @@ export class CompilerUtil {
|
|
|
34
39
|
* Rewrite's sourcemap locations to real folders
|
|
35
40
|
* @returns
|
|
36
41
|
*/
|
|
37
|
-
static rewriteSourceMap(
|
|
38
|
-
const data: { sourceRoot
|
|
39
|
-
const
|
|
42
|
+
static rewriteSourceMap(ctx: ManifestContext, text: string, outputToSource: OutputToSource): string {
|
|
43
|
+
const data: { sourceRoot?: string, sources: string[] } = JSON.parse(text);
|
|
44
|
+
const output = this.inputToOutput(path.resolve(ctx.workspacePath, ctx.outputFolder, data.sources[0]));
|
|
45
|
+
const { source: file } = outputToSource(output) ?? {};
|
|
40
46
|
|
|
41
|
-
const { source: file } = inputToSource(src) ?? {};
|
|
42
47
|
if (file) {
|
|
43
|
-
data.sourceRoot
|
|
48
|
+
delete data.sourceRoot;
|
|
44
49
|
data.sources = [file];
|
|
45
50
|
text = JSON.stringify(data);
|
|
46
51
|
}
|
|
47
|
-
|
|
48
52
|
return text;
|
|
49
53
|
}
|
|
50
54
|
|
|
51
55
|
/**
|
|
52
56
|
* Rewrite's inline sourcemap locations to real folders
|
|
53
57
|
* @param text
|
|
54
|
-
* @param
|
|
58
|
+
* @param outputToSource
|
|
55
59
|
* @param writeData
|
|
56
60
|
* @returns
|
|
57
61
|
*/
|
|
58
62
|
static rewriteInlineSourceMap(
|
|
59
|
-
|
|
63
|
+
ctx: ManifestContext,
|
|
60
64
|
text: string,
|
|
61
|
-
|
|
65
|
+
outputToSource: OutputToSource,
|
|
62
66
|
{ sourceMapUrlPos }: ts.WriteFileCallbackData & { sourceMapUrlPos: number }
|
|
63
67
|
): string {
|
|
64
68
|
const sourceMapUrl = text.substring(sourceMapUrlPos);
|
|
65
69
|
const [prefix, sourceMapData] = sourceMapUrl.split('base64,');
|
|
66
|
-
const rewritten = this.rewriteSourceMap(
|
|
70
|
+
const rewritten = this.rewriteSourceMap(ctx, Buffer.from(sourceMapData, 'base64url').toString('utf8'), outputToSource);
|
|
67
71
|
return [
|
|
68
72
|
text.substring(0, sourceMapUrlPos),
|
|
69
73
|
prefix,
|
package/src/watch.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import fs from 'fs/promises';
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ManifestContext, ManifestModuleUtil, ManifestUtil, WatchEvent, ManifestModuleFolderType,
|
|
6
|
+
ManifestModuleFileType, path, ManifestModule, watchFolders, WatchEventListener
|
|
7
|
+
} from '@travetto/manifest';
|
|
8
|
+
import { getManifestContext } from '@travetto/manifest/bin/context';
|
|
9
|
+
|
|
10
|
+
import { CompilerState } from './state';
|
|
11
|
+
import { CompilerUtil } from './util';
|
|
12
|
+
import { CompileEmitter, CompileWatcherHandler } from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Utils for watching
|
|
16
|
+
*/
|
|
17
|
+
export class CompilerWatcher {
|
|
18
|
+
|
|
19
|
+
#sourceHashes = new Map<string, number>();
|
|
20
|
+
#manifestContexts = new Map<string, ManifestContext>();
|
|
21
|
+
#dirtyFiles: { modFolder: string, mod: string, moduleFile?: string, folderKey?: ManifestModuleFolderType, type?: ManifestModuleFileType }[] = [];
|
|
22
|
+
#state: CompilerState;
|
|
23
|
+
|
|
24
|
+
constructor(state: CompilerState) {
|
|
25
|
+
this.#state = state;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async #rebuildManifestsIfNeeded(): Promise<void> {
|
|
29
|
+
if (!this.#dirtyFiles.length) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const mods = [...new Set(this.#dirtyFiles.map(x => x.modFolder))];
|
|
33
|
+
const contexts = await Promise.all(mods.map(async folder => {
|
|
34
|
+
if (!this.#manifestContexts.has(folder)) {
|
|
35
|
+
const ctx = await getManifestContext(folder);
|
|
36
|
+
this.#manifestContexts.set(folder, ctx);
|
|
37
|
+
}
|
|
38
|
+
return this.#manifestContexts.get(folder)!;
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
const files = this.#dirtyFiles;
|
|
42
|
+
this.#dirtyFiles = [];
|
|
43
|
+
|
|
44
|
+
for (const ctx of [...contexts, this.#state.manifest]) {
|
|
45
|
+
const newManifest = await ManifestUtil.buildManifest(ctx);
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
if (
|
|
48
|
+
file.folderKey && file.moduleFile && file.type &&
|
|
49
|
+
file.mod in newManifest.modules && file.folderKey in newManifest.modules[file.mod].files
|
|
50
|
+
) {
|
|
51
|
+
newManifest.modules[file.mod].files[file.folderKey]!.push([file.moduleFile, file.type, Date.now()]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
await ManifestUtil.writeManifest(ctx, newManifest);
|
|
55
|
+
}
|
|
56
|
+
// Reindex
|
|
57
|
+
this.#state.manifestIndex.init(this.#state.manifestIndex.manifestFile);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#getModuleMap(): Record<string, ManifestModule> {
|
|
61
|
+
return Object.fromEntries(
|
|
62
|
+
Object.values(this.#state.manifest.modules).map(x => [path.resolve(this.#state.manifest.workspacePath, x.sourceFolder), x])
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get a watcher for a given compiler state
|
|
68
|
+
* @param state
|
|
69
|
+
* @param handler
|
|
70
|
+
* @returns
|
|
71
|
+
*/
|
|
72
|
+
#getWatcher(handler: CompileWatcherHandler): WatchEventListener {
|
|
73
|
+
const mods = this.#getModuleMap();
|
|
74
|
+
|
|
75
|
+
return async ({ file: sourceFile, action }: WatchEvent, folder: string): Promise<void> => {
|
|
76
|
+
const mod = mods[folder];
|
|
77
|
+
const moduleFile = sourceFile.includes(mod.sourceFolder) ? sourceFile.split(`${mod.sourceFolder}/`)[1] : sourceFile;
|
|
78
|
+
switch (action) {
|
|
79
|
+
case 'create': {
|
|
80
|
+
const fileType = ManifestModuleUtil.getFileType(moduleFile);
|
|
81
|
+
this.#dirtyFiles.push({
|
|
82
|
+
mod: mod.name,
|
|
83
|
+
modFolder: folder,
|
|
84
|
+
moduleFile,
|
|
85
|
+
folderKey: ManifestModuleUtil.getFolderKey(sourceFile),
|
|
86
|
+
type: ManifestModuleUtil.getFileType(sourceFile)
|
|
87
|
+
});
|
|
88
|
+
if (CompilerUtil.validFile(fileType)) {
|
|
89
|
+
await this.#rebuildManifestsIfNeeded();
|
|
90
|
+
|
|
91
|
+
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
92
|
+
const input = this.#state.registerInput(mod, moduleFile);
|
|
93
|
+
this.#sourceHashes.set(sourceFile, hash);
|
|
94
|
+
handler.create(input);
|
|
95
|
+
}
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case 'update': {
|
|
99
|
+
await this.#rebuildManifestsIfNeeded();
|
|
100
|
+
const entry = this.#state.getBySource(sourceFile);
|
|
101
|
+
if (entry) {
|
|
102
|
+
const hash = CompilerUtil.naiveHash(readFileSync(sourceFile, 'utf8'));
|
|
103
|
+
if (this.#sourceHashes.get(sourceFile) !== hash) {
|
|
104
|
+
this.#state.resetInputSource(entry.input);
|
|
105
|
+
this.#sourceHashes.set(sourceFile, hash);
|
|
106
|
+
handler.update(entry.input);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
break;
|
|
110
|
+
}
|
|
111
|
+
case 'delete': {
|
|
112
|
+
const entry = this.#state.getBySource(sourceFile);
|
|
113
|
+
if (entry) {
|
|
114
|
+
this.#state.removeInput(entry.input);
|
|
115
|
+
if (entry.output) {
|
|
116
|
+
this.#dirtyFiles.push({ mod: mod.name, modFolder: folder });
|
|
117
|
+
handler.delete(entry.output);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Watch files based on root index
|
|
127
|
+
*/
|
|
128
|
+
watchFiles(emit: CompileEmitter): Promise<() => Promise<void>> {
|
|
129
|
+
return watchFolders(
|
|
130
|
+
this.#state.manifestIndex.getLocalInputFolderMapping(),
|
|
131
|
+
this.#getWatcher({
|
|
132
|
+
create: emit,
|
|
133
|
+
update: emit,
|
|
134
|
+
delete: (outputFile) => fs.rm(outputFile, { force: true })
|
|
135
|
+
}),
|
|
136
|
+
{
|
|
137
|
+
filter: ev => ev.file.endsWith('.ts') || ev.file.endsWith('.js') || ev.file.endsWith('package.json'),
|
|
138
|
+
ignore: ['node_modules']
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|