@travetto/compiler 3.0.0-rc.2 → 3.0.0-rc.21

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 ADDED
@@ -0,0 +1,213 @@
1
+ import ts from 'typescript';
2
+ import os from 'os';
3
+
4
+ import { path, ManifestModuleUtil, ManifestModule, ManifestRoot, ManifestIndex } from '@travetto/manifest';
5
+ import { TransformerManager } from '@travetto/transformer';
6
+
7
+ import { CompilerUtil } from './util';
8
+ import { TranspileUtil } from '../support/transpile';
9
+ import { CompileStateEntry } from './types';
10
+
11
+ export class CompilerState implements ts.CompilerHost {
12
+
13
+ static async get(idx: ManifestIndex): Promise<CompilerState> {
14
+ return new CompilerState().init(idx);
15
+ }
16
+
17
+ private constructor() { }
18
+
19
+ #rootDir = path.resolve(os.tmpdir(), '_');
20
+ #outputPath: string;
21
+ #inputFiles = new Set<string>();
22
+ #inputDirectoryToSource = new Map<string, string>();
23
+ #inputToEntry = new Map<string, CompileStateEntry>();
24
+ #sourceToEntry = new Map<string, CompileStateEntry>();
25
+ #outputToEntry = new Map<string, CompileStateEntry>();
26
+
27
+ #sourceContents = new Map<string, string | undefined>();
28
+ #sourceFileObjects = new Map<string, ts.SourceFile>();
29
+
30
+ #manifestIndex: ManifestIndex;
31
+ #manifest: ManifestRoot;
32
+ #modules: ManifestModule[];
33
+ #transformerManager: TransformerManager;
34
+ #compilerOptions: ts.CompilerOptions;
35
+
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);
40
+ this.#modules = Object.values(this.#manifest.modules);
41
+
42
+ // Register all inputs
43
+ for (const x of this.#modules) {
44
+ const files = [
45
+ ...x.files.bin ?? [],
46
+ ...x.files.src ?? [],
47
+ ...x.files.support ?? [],
48
+ ...x.files.doc ?? [],
49
+ ...x.files.test ?? [],
50
+ ...x.files.$index ?? [],
51
+ ...x.files.$package ?? []
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);
61
+
62
+ this.#compilerOptions = {
63
+ ...await TranspileUtil.getCompilerOptions(this.#manifest),
64
+ rootDir: this.#rootDir,
65
+ outDir: this.#outputPath
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
+ }
105
+ }
106
+
107
+ getBySource(sourceFile: string): CompileStateEntry | undefined {
108
+ return this.#sourceToEntry.get(sourceFile);
109
+ }
110
+
111
+ registerInput(module: ManifestModule, moduleFile: string): string {
112
+ const relativeInput = `${module.outputFolder}/${moduleFile}`;
113
+ const sourceFile = path.toPosix(path.resolve(this.#manifest.workspacePath, module.sourceFolder, moduleFile));
114
+ const sourceFolder = path.dirname(sourceFile);
115
+ const inputFile = path.resolve(this.#rootDir, relativeInput); // Ensure input is isolated
116
+ const inputFolder = path.dirname(inputFile);
117
+ const fileType = ManifestModuleUtil.getFileType(moduleFile);
118
+ const outputFile = fileType === 'typings' ?
119
+ undefined :
120
+ path.resolve(this.#outputPath, CompilerUtil.inputToOutput(relativeInput));
121
+
122
+ const entry = { source: sourceFile, input: inputFile, output: outputFile, module, relativeInput };
123
+
124
+ this.#inputToEntry.set(inputFile, entry);
125
+ this.#sourceToEntry.set(sourceFile, entry);
126
+ this.#inputDirectoryToSource.set(inputFolder, sourceFolder);
127
+
128
+ if (outputFile) {
129
+ this.#outputToEntry.set(outputFile, entry);
130
+ }
131
+
132
+ this.#inputFiles.add(inputFile);
133
+
134
+ return inputFile;
135
+ }
136
+
137
+ removeInput(inputFile: string): void {
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);
144
+ this.#inputFiles.delete(inputFile);
145
+ }
146
+
147
+ resetInputSource(inputFile: string): void {
148
+ this.#sourceFileObjects.delete(inputFile);
149
+ this.#sourceContents.delete(inputFile);
150
+ }
151
+
152
+ getAllFiles(): string[] {
153
+ return [...this.#inputFiles];
154
+ }
155
+
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);
162
+ }
163
+
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));
174
+ }
175
+
176
+ directoryExists(inputDir: string): boolean {
177
+ return this.#inputDirectoryToSource.has(inputDir) || ts.sys.directoryExists(this.translateRawInput(inputDir));
178
+ }
179
+
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)!;
212
+ }
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 ADDED
@@ -0,0 +1,147 @@
1
+ import ts from 'typescript';
2
+
3
+ import { ManifestContext, ManifestModuleFileType, ManifestRoot, Package, path } from '@travetto/manifest';
4
+
5
+ type OutputToSource = (outputFile: string) => ({ source: string } | undefined);
6
+ export type FileWatchEvent = { type: 'create' | 'delete' | 'update', path: string };
7
+
8
+ const nativeCwd = process.cwd();
9
+
10
+ /**
11
+ * Standard utilities for compiler
12
+ */
13
+ export class CompilerUtil {
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
+
20
+ /**
21
+ * Map input file to output format, generally converting ts extensions to js
22
+ * @param file
23
+ * @returns
24
+ */
25
+ static inputToOutput(file: string): string {
26
+ return file.replace(/[.][tj]s$/, '.js');
27
+ }
28
+
29
+ /**
30
+ * Determines if write callback data has sourcemap information
31
+ * @param data
32
+ * @returns
33
+ */
34
+ static isSourceMapUrlPosData(data?: ts.WriteFileCallbackData): data is { sourceMapUrlPos: number } {
35
+ return data !== undefined && data !== null && typeof data === 'object' && ('sourceMapUrlPos' in data);
36
+ }
37
+
38
+ /**
39
+ * Rewrite's sourcemap locations to real folders
40
+ * @returns
41
+ */
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) ?? {};
46
+
47
+ if (file) {
48
+ delete data.sourceRoot;
49
+ data.sources = [file];
50
+ text = JSON.stringify(data);
51
+ }
52
+ return text;
53
+ }
54
+
55
+ /**
56
+ * Rewrite's inline sourcemap locations to real folders
57
+ * @param text
58
+ * @param outputToSource
59
+ * @param writeData
60
+ * @returns
61
+ */
62
+ static rewriteInlineSourceMap(
63
+ ctx: ManifestContext,
64
+ text: string,
65
+ outputToSource: OutputToSource,
66
+ { sourceMapUrlPos }: ts.WriteFileCallbackData & { sourceMapUrlPos: number }
67
+ ): string {
68
+ const sourceMapUrl = text.substring(sourceMapUrlPos);
69
+ const [prefix, sourceMapData] = sourceMapUrl.split('base64,');
70
+ const rewritten = this.rewriteSourceMap(ctx, Buffer.from(sourceMapData, 'base64url').toString('utf8'), outputToSource);
71
+ return [
72
+ text.substring(0, sourceMapUrlPos),
73
+ prefix,
74
+ 'base64,',
75
+ Buffer.from(rewritten, 'utf8').toString('base64url')
76
+ ].join('');
77
+ }
78
+
79
+ /**
80
+ * Rewrites the package.json to target .js files instead of .ts files, and pins versions
81
+ * @param manifest
82
+ * @param file
83
+ * @param text
84
+ * @returns
85
+ */
86
+ static rewritePackageJSON(manifest: ManifestRoot, text: string): string {
87
+ const pkg: Package = JSON.parse(text);
88
+ if (pkg.files) {
89
+ pkg.files = pkg.files.map(x => this.inputToOutput(x));
90
+ }
91
+ if (pkg.main) {
92
+ pkg.main = this.inputToOutput(pkg.main);
93
+ }
94
+ pkg.type = manifest.moduleType;
95
+ for (const key of ['devDependencies', 'dependencies', 'peerDependencies'] as const) {
96
+ if (key in pkg) {
97
+ for (const dep of Object.keys(pkg[key] ?? {})) {
98
+ if (dep in manifest.modules) {
99
+ pkg[key]![dep] = manifest.modules[dep].version;
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return JSON.stringify(pkg, null, 2);
105
+ }
106
+
107
+ /**
108
+ * Build transpilation error
109
+ * @param filename The name of the file
110
+ * @param diagnostics The diagnostic errors
111
+ */
112
+ static buildTranspileError(filename: string, diagnostics: Error | readonly ts.Diagnostic[]): Error {
113
+ if (diagnostics instanceof Error) {
114
+ return diagnostics;
115
+ }
116
+
117
+ const errors: string[] = diagnostics.slice(0, 5).map(diag => {
118
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
119
+ const message = ts.flattenDiagnosticMessageText(diag.messageText, '\n');
120
+ if (diag.file) {
121
+ const { line, character } = diag.file.getLineAndCharacterOfPosition(diag.start!);
122
+ return ` @ ${diag.file.fileName.replace(nativeCwd, '.')}(${line + 1}, ${character + 1}): ${message}`;
123
+ } else {
124
+ return ` ${message}`;
125
+ }
126
+ });
127
+
128
+ if (diagnostics.length > 5) {
129
+ errors.push(`${diagnostics.length - 5} more ...`);
130
+ }
131
+ return new Error(`Transpiling ${filename.replace(nativeCwd, '.')} failed: \n${errors.join('\n')}`);
132
+ }
133
+
134
+ /**
135
+ * Naive hashing
136
+ */
137
+ static naiveHash(text: string): number {
138
+ let hash = 5381;
139
+
140
+ for (let i = 0; i < text.length; i++) {
141
+ // eslint-disable-next-line no-bitwise
142
+ hash = (hash * 33) ^ text.charCodeAt(i);
143
+ }
144
+
145
+ return Math.abs(hash);
146
+ }
147
+ }
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
+ }
@@ -0,0 +1,2 @@
1
+ import { Compiler } from '../src/compiler';
2
+ Compiler.main();
@@ -0,0 +1,139 @@
1
+ import fs from 'fs/promises';
2
+ import path from 'path';
3
+
4
+ import type { ManifestContext } from '@travetto/manifest';
5
+
6
+ import { TranspileUtil, CompileResult } from './transpile';
7
+ import { LockManager } from './lock';
8
+ import { LogUtil } from './log';
9
+
10
+ const SOURCE_SEED = ['package.json', 'index.ts', '__index__.ts', 'src', 'support', 'bin'];
11
+ const PRECOMPILE_MODS = ['@travetto/terminal', '@travetto/manifest', '@travetto/transformer', '@travetto/compiler'];
12
+
13
+ const importManifest = (ctx: ManifestContext): Promise<typeof import('@travetto/manifest')> =>
14
+ import(path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/manifest/__index__.js'));
15
+
16
+ /**
17
+ * Run the compiler
18
+ */
19
+ async function compile(ctx: ManifestContext, op: 'watch' | 'build' | undefined, onMessage: (msg: unknown) => void): Promise<CompileResult> {
20
+ let changes = 0;
21
+
22
+ await LogUtil.withLogger('precompile', async () => {
23
+ for (const mod of PRECOMPILE_MODS) {
24
+ changes += (await TranspileUtil.compileIfStale(ctx, 'precompile', mod, SOURCE_SEED)).length;
25
+ }
26
+ });
27
+
28
+ const { ManifestUtil, ManifestDeltaUtil, PackageUtil } = await importManifest(ctx);
29
+
30
+ PackageUtil.clearCache();
31
+
32
+ const manifest = await LogUtil.withLogger('manifest', async () => ManifestUtil.buildManifest(ctx));
33
+
34
+ await LogUtil.withLogger('transformers', async () => {
35
+ for (const mod of Object.values(manifest.modules).filter(m => m.files.$transformer?.length)) {
36
+ changes += (await TranspileUtil.compileIfStale(ctx, 'transformers', mod.name, ['package.json', ...mod.files.$transformer!.map(x => x[0])])).length;
37
+ }
38
+ });
39
+
40
+ const delta = await LogUtil.withLogger('delta', async log => {
41
+ if (changes) {
42
+ log('debug', 'Skipping, everything changed');
43
+ return [{ type: 'changed', file: '*', module: ctx.mainModule } as const];
44
+ } else {
45
+ return ManifestDeltaUtil.produceDelta(ctx, manifest);
46
+ }
47
+ });
48
+
49
+ if (changes) {
50
+ await LogUtil.withLogger('reset', async log => {
51
+ await fs.rm(path.resolve(ctx.workspacePath, ctx.outputFolder), { recursive: true, force: true });
52
+ log('info', 'Clearing output due to compiler changes');
53
+ }, false);
54
+ }
55
+
56
+ // Write manifest
57
+ await LogUtil.withLogger('manifest', async log => {
58
+ await ManifestUtil.writeManifest(ctx, manifest);
59
+ log('debug', `Wrote manifest ${ctx.mainModule}`);
60
+
61
+ // Update all manifests
62
+ if (delta.length && ctx.monoRepo && !ctx.mainFolder) {
63
+ const names: string[] = [];
64
+ const mods = Object.values(manifest.modules).filter(x => x.local && x.name !== ctx.mainModule);
65
+ for (const mod of mods) {
66
+ await ManifestUtil.rewriteManifest(path.resolve(ctx.workspacePath, mod.sourceFolder));
67
+ names.push(mod.name);
68
+ }
69
+ log('debug', `Changes triggered ${delta.slice(0, 10).map(x => `${x.type}:${x.module}:${x.file}`)}`);
70
+ log('debug', `Rewrote monorepo manifests [changes=${delta.length}] ${names.slice(0, 10).join(', ')}`);
71
+ }
72
+ });
73
+
74
+ return await LogUtil.withLogger('compile', async log => {
75
+ const changed = delta.filter(x => x.type === 'added' || x.type === 'changed');
76
+ log('debug', `Started action=${op} changed=${changed.slice(0, 10).map(x => `${x.module}/${x.file}`)}`);
77
+ if (changed.length || op === 'watch') {
78
+ const res = await TranspileUtil.runCompiler(ctx, manifest, changed, op === 'watch', onMessage);
79
+ log('debug', 'Finished');
80
+ return res;
81
+ } else {
82
+ log('debug', 'Skipped');
83
+ return 'skipped';
84
+ }
85
+ }, false);
86
+ }
87
+
88
+ /**
89
+ * Export manifests
90
+ */
91
+ async function exportManifest(ctx: ManifestContext, output?: string, env = 'dev'): Promise<void> {
92
+ const { ManifestUtil } = await importManifest(ctx);
93
+ let manifest = await ManifestUtil.buildManifest(ctx);
94
+
95
+ // If in prod mode, only include std modules
96
+ if (/^prod/i.test(env)) {
97
+ manifest = ManifestUtil.createProductionManifest(manifest);
98
+ }
99
+ if (output) {
100
+ output = await ManifestUtil.writeManifestToFile(output, manifest);
101
+ LogUtil.log('manifest', [], 'info', `Wrote manifest ${output}`);
102
+ } else {
103
+ console.log(JSON.stringify(manifest, null, 2));
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Launch
109
+ */
110
+ export async function launch(ctx: ManifestContext, root: ManifestContext, op?: 'build' | 'watch' | 'manifest', args: (string | undefined)[] = []): Promise<void> {
111
+ if (op !== 'manifest' && await LockManager.getCompileAction(root, op) === 'build') {
112
+ await LockManager.withLocks(root, async (acquire, release) => {
113
+ let action: CompileResult;
114
+ do {
115
+ acquire(op ?? 'build');
116
+ if (op === 'watch') {
117
+ acquire('build');
118
+ }
119
+ action = await compile(root, op, msg => {
120
+ switch (msg) {
121
+ case 'build-complete': release('build'); break;
122
+ }
123
+ });
124
+ } while (action === 'restart');
125
+ });
126
+ }
127
+
128
+ switch (op) {
129
+ case 'manifest': return exportManifest(ctx, ...args);
130
+ case 'build': return LogUtil.log('build', [], 'info', 'Successfully built');
131
+ case undefined: {
132
+ // TODO: Externalize somehow?
133
+ const outputPath = path.resolve(ctx.workspacePath, ctx.outputFolder);
134
+ process.env.TRV_MANIFEST = path.resolve(outputPath, 'node_modules', ctx.mainModule);
135
+ const cliMain = path.join(outputPath, 'node_modules', '@travetto/cli/support/cli.js');
136
+ return import(cliMain);
137
+ }
138
+ }
139
+ }
@@ -0,0 +1,25 @@
1
+ import { workerData, parentPort } from 'worker_threads';
2
+ import { utimesSync } from 'fs';
3
+
4
+ const data: { files: string[], interval: number } = workerData;
5
+ const files = data.files;
6
+
7
+ const interval = setInterval(() => {
8
+ const now = Date.now() / 1000;
9
+ for (const file of files) {
10
+ try {
11
+ utimesSync(file, now, now);
12
+ } catch { }
13
+ }
14
+ }, data.interval);
15
+
16
+
17
+ parentPort?.on('message', val => {
18
+ if (val === 'stop') {
19
+ files.splice(0, files.length);
20
+ clearInterval(interval);
21
+ } else if (val && typeof val === 'object' && 'files' in val && Array.isArray(val.files)) {
22
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
23
+ files.splice(0, files.length, ...val.files as string[]);
24
+ }
25
+ });