@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/src/state.ts CHANGED
@@ -1,35 +1,47 @@
1
1
  import ts from 'typescript';
2
- import { readFileSync } from 'fs';
2
+ import os from 'os';
3
3
 
4
- import { path, ManifestModuleUtil, ManifestModule, ManifestModuleFileType, ManifestRoot, WatchEvent } from '@travetto/manifest';
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
- const validFile = (type: ManifestModuleFileType): boolean => type === 'ts' || type === 'package-json' || type === 'js';
11
+ export class CompilerState implements ts.CompilerHost {
10
12
 
11
- export class CompilerState {
13
+ static async get(idx: ManifestIndex): Promise<CompilerState> {
14
+ return new CompilerState().init(idx);
15
+ }
16
+
17
+ private constructor() { }
12
18
 
13
- #inputFiles: Set<string>;
14
- #inputToSource = new Map<string, string>();
15
- #stagedOutputToOutput = new Map<string, string>();
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
- #sourceInputOutput = new Map<string, { source: string, input: string, stagedOutput?: string, output?: string, module: ManifestModule }>();
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
- #transformers: string[];
33
+ #transformerManager: TransformerManager;
34
+ #compilerOptions: ts.CompilerOptions;
27
35
 
28
- constructor(manifest: ManifestRoot) {
29
- this.#manifest = manifest;
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
- this.#inputFiles = new Set(this.#modules.flatMap(
32
- x => [
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
- .filter(([file, type]) => validFile(type) || type === 'typings')
42
- .map(([f]) => this.registerInput(x, f))
43
- ));
44
-
45
- this.#transformers = this.#modules.flatMap(
46
- x => (x.files.$transformer ?? []).map(([f]) =>
47
- path.resolve(manifest.workspacePath, x.sourceFolder, f)
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
- async getCompilerOptions(): Promise<ts.CompilerOptions> {
53
- return {
62
+ this.#compilerOptions = {
54
63
  ...await TranspileUtil.getCompilerOptions(this.#manifest),
55
- outDir: this.#manifest.workspacePath, // Force to root
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
- resolveInput(file: string): string {
60
- return this.#sourceInputOutput.get(file)!.input;
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.#manifest.workspacePath, '##', relativeInput); // Ensure input is isolated
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
- // Rewrite stagedOutput to final output form
79
- const stagedOutputFile = CompilerUtil.inputToOutput(inputFile);
122
+ const entry = { source: sourceFile, input: inputFile, output: outputFile, module, relativeInput };
80
123
 
81
- this.#inputToSource.set(inputFile, sourceFile);
82
- this.#sourceInputOutput.set(sourceFile, { source: sourceFile, input: inputFile, stagedOutput: stagedOutputFile, output: outputFile, module });
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 (stagedOutputFile) {
87
- this.#stagedOutputToOutput.set(stagedOutputFile, outputFile!);
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.#inputToSource.get(inputFile)!;
96
- const { stagedOutput } = this.#sourceInputOutput.get(source)!;
97
- this.#stagedOutputToOutput.delete(stagedOutput!);
98
- this.#sourceInputOutput.delete(source);
99
- this.#inputToSource.delete(inputFile);
100
- this.#inputToOutput.delete(inputFile);
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
- get modules(): ManifestModule[] {
110
- return this.#modules;
152
+ getAllFiles(): string[] {
153
+ return [...this.#inputFiles];
111
154
  }
112
155
 
113
- get transformers(): string[] {
114
- return this.#transformers;
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
- getAllFiles(): string[] {
118
- return [...this.#inputFiles];
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
- // Build watcher
122
- getWatcher(handler: {
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
- // ts.CompilerHost
168
- getCompilerHost(options: ts.CompilerOptions): ts.CompilerHost {
169
- const host: ts.CompilerHost = {
170
- getCanonicalFileName: (file: string): string => file,
171
- getCurrentDirectory: path.cwd,
172
- getDefaultLibFileName: (opts: ts.CompilerOptions): string => ts.getDefaultLibFileName(opts),
173
- getNewLine: (): string => ts.sys.newLine,
174
- useCaseSensitiveFileNames: (): boolean => ts.sys.useCaseSensitiveFileNames,
175
- getDefaultLibLocation: (): string => path.dirname(ts.getDefaultLibFilePath(options)),
176
- fileExists: (inputFile: string): boolean => this.#inputToSource.has(inputFile) || ts.sys.fileExists(inputFile),
177
- directoryExists: (inputFolder: string): boolean => this.#inputDirectoryToSource.has(inputFolder) || ts.sys.directoryExists(inputFolder),
178
- readFile: (inputFile: string): string | undefined => {
179
- const res = this.#sourceContents.get(inputFile) ?? ts.sys.readFile(this.#inputToSource.get(inputFile) ?? inputFile);
180
- this.#sourceContents.set(inputFile, res);
181
- return res;
182
- },
183
- writeFile: (
184
- outputFile: string,
185
- text: string,
186
- bom: boolean,
187
- onError?: (message: string) => void,
188
- sourceFiles?: readonly ts.SourceFile[],
189
- data?: ts.WriteFileCallbackData
190
- ): void => {
191
- if (outputFile.endsWith('package.json')) {
192
- text = CompilerUtil.rewritePackageJSON(this.#manifest, text);
193
- } else if (!options.inlineSourceMap && options.sourceMap && outputFile.endsWith('.map')) {
194
- text = CompilerUtil.rewriteSourceMap(this.#manifest.workspacePath, text, f => this.#sourceInputOutput.get(this.#inputToSource.get(f)!));
195
- } else if (options.inlineSourceMap && CompilerUtil.isSourceMapUrlPosData(data)) {
196
- text = CompilerUtil.rewriteInlineSourceMap(this.#manifest.workspacePath, text, f => this.#sourceInputOutput.get(this.#inputToSource.get(f)!), data);
197
- }
198
- outputFile = this.#stagedOutputToOutput.get(outputFile) ?? outputFile;
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 InputToSource = (inputFile: string) => ({ source: string } | undefined);
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(root: string, text: string, inputToSource: InputToSource): string {
38
- const data: { sourceRoot: string, sources: string[] } = JSON.parse(text);
39
- const src = path.resolve(data.sourceRoot, data.sources[0]);
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 = root;
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 inputToSource
58
+ * @param outputToSource
55
59
  * @param writeData
56
60
  * @returns
57
61
  */
58
62
  static rewriteInlineSourceMap(
59
- root: string,
63
+ ctx: ManifestContext,
60
64
  text: string,
61
- inputToSource: InputToSource,
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(root, Buffer.from(sourceMapData, 'base64url').toString('utf8'), inputToSource);
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
+ }