@travetto/compiler 3.0.0-rc.9 → 3.0.0

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 CHANGED
@@ -1,28 +1,82 @@
1
1
  <!-- This file was generated by @travetto/doc and should not be modified directly -->
2
2
  <!-- Please modify https://github.com/travetto/travetto/tree/main/module/compiler/DOC.ts and execute "npx trv doc" to rebuild -->
3
3
  # Compiler
4
- ## Compiler
4
+ ## The compiler infrastructure for the Travetto framework
5
5
 
6
6
  **Install: @travetto/compiler**
7
7
  ```bash
8
8
  npm install @travetto/compiler
9
+
10
+ # or
11
+
12
+ yarn add @travetto/compiler
9
13
  ```
10
14
 
11
- This module expands upon [Typescript](https://typescriptlang.org), with supplemental functionality:
15
+ This module expands upon the [Typescript](https://typescriptlang.org) compiler, with the additional features:
12
16
 
13
- * Read `tsconfig.json` from the project directory to provide
14
- * Supports on-the-fly compilation, nothing needs to be compiled ahead of time
15
- * Enhanced AST transformations, and transformer registration [object Object]
16
- * Intelligent caching of source files to minimize recompilation
17
- * Support for detecting changes in sources files at runtime
18
- * Allows for hot-reloading of classes during development
19
- * Utilizes `es2015` `Proxy`s to allow for swapping out implementation at runtime
20
- Additionally, there is support for common AST transformations via [Transformation](https://github.com/travetto/travetto/tree/main/module/transformer#readme "Functionality for AST transformations, with transformer registration, and general utils")
21
- ## Debugging
22
- When dealing with transformers, logging is somewhat tricky as the compiler executes before the code is loaded. To that end, the file `compiler.log` is created in the cache directory during the compilation process. This is a location that transformers should be free to log to, for debugging, and any additional feedback.
17
+ * Integration with the [Transformation](https://github.com/travetto/travetto/tree/main/module/transformer#readme "Functionality for AST transformations, with transformer registration, and general utils") module, allowing for rich, type-aware transformations
18
+ * Automatic conversion to either [Ecmascript Module](https://nodejs.org/api/esm.html) or [CommonJS](https://nodejs.org/api/modules.html) based on the [Package JSON](https://docs.npmjs.com/cli/v9/configuring-npm/package-json) `type` value
19
+ * Removal of type only imports which can break [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
20
+ * Automatic addition of `.js` extension to imports to also support [Ecmascript Module](https://nodejs.org/api/esm.html)-style output
21
+
22
+ Beyond the [Typescript](https://typescriptlang.org) compiler functionality, the module provides the primary entry point into the development process.
23
23
 
24
24
  ## CLI
25
25
 
26
- The module provides the ability to clear the compilation cache to handle any inconsistencies that may arise.
26
+ The cli, [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) is a compilation aware entry point, that has the ability to check for active builds, and ongoing watch operations to ensure only one process is building at a time. Within the framework, regardless of mono-repo or not, always builds the entire project. With the efficient caching behavior, this leads to generally a minimal overhead but allows for centralization of all operations.
27
+
28
+ The CLI supports the following operations:
29
+
30
+
31
+ * `clean` - Removes the output folder, and if `-a` is also passed, will also clean out the compiler folder
32
+ * `build` - Will attempt to build the project. If the project is already built, will return immediately. If the project is being built somewhere else, will wait until a build is completed.
33
+ * `watch` - If nothing else is watching, will start the watch operation. Otherwise will return immediately.
34
+ * `manifest` - Will produce a manifest. If no file is passed in the command line arguments, will output to stdout
35
+ * `<other>` - Will be delegated to the [Command Line Interface](https://github.com/travetto/travetto/tree/main/module/cli#readme "CLI infrastructure for Travetto framework") entry point after a successful build.
36
+
37
+ In addition to the normal output, the compiler supports an environment variable `TRV_BUILD` that supports the following values: `debug`, `info`, `warn` or `none`. This provides different level of logging during the build process which is helpful to diagnose any odd behaviors. When invoking an unknown command (e.g. `<other>` from above), the default level is `warn`. Otherwise the default logging level is `info`.
38
+
39
+ **Terminal: Sample trv output with debug logging**
40
+ ```bash
41
+ $ TRV_BUILD=debug trv build
42
+
43
+ 2029-03-14T04:00:00.618Z [lock ] watch pid=000000 Started
44
+ 2029-03-14T04:00:00.837Z [lock ] watch pid=000000 Already running, and has built
45
+ 2029-03-14T04:00:01.510Z [lock ] watch pid=000000 Completed
46
+ 2029-03-14T04:00:02.450Z [build ] Successfully built
47
+ ```
48
+
49
+ **Terminal: Sample trv output with default log level**
50
+ ```bash
51
+ $ trv build
52
+ ```
53
+
54
+ ## Compilation Architecture
55
+
56
+ The compiler will move through the following phases on a given compilation execution:
57
+
58
+ * `Bootstrapping` - Initial compilation of [Compiler](https://github.com/travetto/travetto/tree/main/module/compiler#readme "The compiler infrastructure for the Travetto framework")'s `support/*.ts` files
59
+ * `Lock Management` - Manages cross-process interaction to ensure single compiler
60
+ * `Build Compiler` - Leverages [Typescript](https://typescriptlang.org) to build files needed to execute compiler
61
+ * `Build Manifest` - Produces the manifest for the given execution
62
+ * `Build Transformers` - Leverages [Typescript](https://typescriptlang.org) to compile all transformers defined in the manifest
63
+ * `Produce Manifest Delta` - Compare the output file system with the manifest to determine what needs to be compiled
64
+ * `Clear all output if needed` - When the compiler source or transformers change, invalidate the entire output
65
+ * `Persist Manifest(s)` - Ensure the manifest is available for the compiler to leverage. Multiple will be written if in a monorepo
66
+ * `Invoke Compiler` - Run [Typescript](https://typescriptlang.org) compiler with the aforementioned enhancements
67
+
68
+ ### Bootstrapping
69
+
70
+ Given that the framework is distributed as [Typescript](https://typescriptlang.org) only files, there is a bootstrapping problem that needs to be mitigated. The [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) entrypoint, along with a small context utility in [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching") are the only [Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript) files needed to run the project. The [trv](https://github.com/travetto/travetto/tree/main/module/compiler/bin/trv.js#L60) entry point will compile `@travetto/compiler/support/*` files as the set that is used at startup. These files are also accessible to the compiler as they get re-compiled after the fact.
71
+
72
+ ### Lock Management
73
+
74
+ The compiler supports invocation from multiple locations at the same time, and provides a layer of orchestration to ensure a single process is building at a time. For a given project, there are four main states:
75
+
76
+
77
+ * No Watch - Building
78
+ * Watch - No Build
79
+ * Watch - Building
80
+ * Inactive / Stale
27
81
 
28
- TODO: Describe cli behavior
82
+ Depending on what state the project is in (depending on various processes), will influence what the supporting tooling should do. [LockManager](https://github.com/travetto/travetto/tree/main/module/compiler/support/lock.ts#L25) represents the majority of the logic for tracking various states, and informing what action should happen when in the above states.
package/__index__.ts CHANGED
@@ -1,3 +1,6 @@
1
1
  export * from './src/compiler';
2
2
  export * from './src/state';
3
- export * from './src/util';
3
+ export * from './src/util';
4
+ export * from './src/watch';
5
+ export * from './src/log';
6
+ export * from './src/types';
package/bin/trv.js CHANGED
@@ -1,81 +1,75 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // @ts-check
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { createRequire } from 'module';
4
7
 
5
- /**
6
- * @param {import('@travetto/manifest').ManifestContext} ctx
7
- * @return {Promise<import('../support/bin/compiler-bootstrap')>}
8
- */
9
- async function $getBootstrap(ctx) {
10
- const path = require('path');
11
- const { buildPackage } = require('./transpile');
8
+ import { getManifestContext } from '@travetto/manifest/bin/context.js';
12
9
 
13
- const root = path.resolve(__dirname, '..');
10
+ const VALID_OPS = { watch: 'watch', build: 'build', clean: 'clean', manifest: 'manifest' };
14
11
 
15
- const loc = await buildPackage(
16
- ctx, '__compiler_bootstrap__', root, 'support/bin/compiler-bootstrap.ts',
17
- ['support/bin/utils.ts', 'bin/transpile.js', 'package.json']
18
- );
19
-
20
- try { return require(loc); } catch { return import(loc); }
21
- }
12
+ const COMPILER_FILES = [...['launcher', 'transpile', 'lock', 'log', 'lock-pinger'].map(x => `support/${x}.js`), 'package.json'];
22
13
 
23
14
  /**
24
15
  * @param {import('@travetto/manifest').ManifestContext} ctx
25
- * @param {boolean} [watch]
16
+ * @return {Promise<import('@travetto/compiler/support/launcher').launch>}
26
17
  */
27
- const compile = (ctx, watch) => $getBootstrap(ctx).then(({ compile: go }) => go(ctx, watch));
28
-
29
- /** @param {string[]} args */
30
- async function exec(args) {
31
- const { getContext } = require('./transpile');
32
- const op = args.find(x => !x.startsWith('-'));
33
- /** @type {{clean?: boolean, quiet?: boolean}} */
34
- const flags = Object.fromEntries(args.filter(x => x.startsWith('-')).map(x => x.split('-').pop()).map(x => [x, true]));
18
+ const $getLauncher = async (ctx) => {
19
+ const tsconfigFile = path.resolve(ctx.workspacePath, 'tsconfig.json');
20
+ if (!(await fs.stat(tsconfigFile).catch(() => undefined))) {
21
+ await fs.writeFile(tsconfigFile, JSON.stringify({ extends: '@travetto/compiler/tsconfig.trv.json' }), 'utf8');
22
+ }
23
+ const compPkg = createRequire(path.resolve(ctx.workspacePath, 'node_modules')).resolve('@travetto/compiler/package.json');
24
+ const files = [];
35
25
 
36
- const ctx = await getContext();
37
- const message = flags.quiet ? () => { } : console.log.bind(console);
26
+ for (const file of COMPILER_FILES) {
27
+ const target = path.resolve(ctx.workspacePath, ctx.compilerFolder, 'node_modules', '@travetto/compiler', file);
28
+ const src = compPkg.replace('package.json', file.replace(/[.]js$/, '.ts'));
38
29
 
39
- // Clean if needed
40
- if (op === 'clean' || (op && flags.clean)) {
41
- const fs = require('fs/promises');
42
- await Promise.all([ctx.outputFolder, ctx.compilerFolder].map(folder =>
43
- fs.rm(`${ctx.workspacePath}/${folder}`, { force: true, recursive: true })));
44
- if (op === 'clean') {
45
- message(`Cleaned ${ctx.workspacePath}: [${ctx.outputFolder}, ${ctx.compilerFolder}]`);
30
+ const targetTime = await fs.stat(target).then(s => Math.max(s.mtimeMs, s.ctimeMs)).catch(() => 0);
31
+ const srcTime = await fs.stat(src).then(s => Math.max(s.mtimeMs, s.ctimeMs));
32
+ // If stale
33
+ if (srcTime > targetTime) {
34
+ const ts = (await import('typescript')).default;
35
+ const module = ctx.moduleType === 'module' ? ts.ModuleKind.ESNext : ts.ModuleKind.CommonJS;
36
+ await fs.mkdir(path.dirname(target), { recursive: true });
37
+ const text = await fs.readFile(src, 'utf8');
38
+ if (file.endsWith('.js')) {
39
+ const content = ts.transpile(
40
+ text,
41
+ { target: ts.ScriptTarget.ES2020, module, esModuleInterop: true, allowSyntheticDefaultImports: true }
42
+ )
43
+ .replace(/^((?:im|ex)port .*from '[.][^']+)(')/mg, (_, a, b) => `${a}.js${b}`)
44
+ .replace(/^(import [^\n]*from '[^.][^\n/]+[/][^\n/]+[/][^\n']+)(')/mg, (_, a, b) => `${a}.js${b}`);
45
+ await fs.writeFile(target, content, 'utf8');
46
+ } else {
47
+ const pkg = JSON.parse(text);
48
+ pkg.type = ctx.moduleType;
49
+ await fs.writeFile(target, JSON.stringify(pkg, null, 2), 'utf8');
50
+ }
51
+ // Compile
46
52
  }
53
+ files.push(target);
47
54
  }
48
55
 
49
- switch (op) {
50
- case 'clean': break;
51
- case 'manifest': {
52
- const { writeManifest, buildManifest } = await $getBootstrap(ctx);
53
- const manifest = (await buildManifest(ctx)).manifest;
54
- await writeManifest(ctx, manifest);
55
- const output = `${ctx.workspacePath}/${ctx.outputFolder}/${ctx.manifestFile}`;
56
- message(`Wrote manifest ${output}`);
57
- break;
58
- }
59
- case 'watch':
60
- message(`Watching ${ctx.workspacePath} for changes...`);
61
- await compile(ctx, true);
62
- return;
63
- case 'build':
64
- await compile(ctx);
65
- message(`Built to ${ctx.workspacePath}/${ctx.outputFolder}`);
66
- break;
67
- default: {
68
- const path = require('path/posix');
69
- const { manifest } = await compile(ctx);
70
- const out = path.join(ctx.workspacePath, ctx.outputFolder);
71
- // TODO: Externalize somehow?
72
- const cliMain = path.join(out, manifest.modules['@travetto/cli'].output, 'support', 'main.cli.js');
73
- process.env.TRV_MANIFEST = ctx.mainModule;
74
- process.env.TRV_OUTPUT = out;
75
- await import(process.env.TRV_MAIN = cliMain);
76
- return;
56
+ try { return require(files[0]).launch; }
57
+ catch { return import(files[0]).then(x => x.launch); }
58
+ };
59
+
60
+ (async () => {
61
+ const ctx = await getManifestContext();
62
+ const [op, args] = [VALID_OPS[process.argv[2]], process.argv.slice(3)];
63
+
64
+ if (op === 'clean') {
65
+ const folders = process.argv.find(x => x === '--all' || x === '-a') ? [ctx.outputFolder, ctx.compilerFolder] : [ctx.outputFolder];
66
+ for (const f of folders) {
67
+ await fs.rm(path.resolve(ctx.workspacePath, f), { force: true, recursive: true });
77
68
  }
69
+ return console.log(`Cleaned ${ctx.workspacePath}: [${folders.join(', ')}]`);
78
70
  }
79
- }
80
71
 
81
- exec(process.argv.slice(2));
72
+ const rootCtx = ctx.monoRepo ? await getManifestContext(ctx.workspacePath) : ctx;
73
+
74
+ return (await $getLauncher(ctx))(ctx, rootCtx, op, args);
75
+ })();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@travetto/compiler",
3
- "version": "3.0.0-rc.9",
4
- "description": "Compiler",
3
+ "version": "3.0.0",
4
+ "description": "The compiler infrastructure for the Travetto framework",
5
5
  "keywords": [
6
6
  "compiler",
7
7
  "travetto",
@@ -13,6 +13,7 @@
13
13
  "email": "travetto.framework@gmail.com",
14
14
  "name": "Travetto Framework"
15
15
  },
16
+ "type": "module",
16
17
  "files": [
17
18
  "__index__.ts",
18
19
  "src",
@@ -29,12 +30,13 @@
29
30
  "directory": "module/compiler"
30
31
  },
31
32
  "dependencies": {
32
- "@travetto/manifest": "^3.0.0-rc.6",
33
- "@travetto/terminal": "^3.0.0-rc.4",
34
- "@travetto/transformer": "^3.0.0-rc.9"
33
+ "@parcel/watcher": "^2.1.0",
34
+ "@travetto/manifest": "^3.0.0",
35
+ "@travetto/terminal": "^3.0.0",
36
+ "@travetto/transformer": "^3.0.0"
35
37
  },
36
38
  "peerDependencies": {
37
- "@travetto/cli": "^3.0.0-rc.7"
39
+ "@travetto/cli": "^3.0.0"
38
40
  },
39
41
  "peerDependenciesMeta": {
40
42
  "@travetto/cli": {
package/src/compiler.ts CHANGED
@@ -1,127 +1,72 @@
1
+ import { install } from 'source-map-support';
1
2
  import ts from 'typescript';
2
3
  import fs from 'fs/promises';
3
- import path from 'path';
4
4
 
5
- import { ManifestState } from '@travetto/manifest';
6
5
  import { GlobalTerminal, TerminalProgressEvent } from '@travetto/terminal';
6
+ import { RootIndex } from '@travetto/manifest';
7
7
 
8
8
  import { CompilerUtil } from './util';
9
9
  import { CompilerState } from './state';
10
-
11
- export type TransformerProvider = {
12
- init(checker: ts.TypeChecker): void;
13
- get(): ts.CustomTransformers | undefined;
14
- };
15
-
16
- type EmitError = Error | readonly ts.Diagnostic[];
17
- type Emitter = (file: string, newProgram?: boolean) => EmitError | undefined;
18
- type EmitEvent = { file: string, i: number, total: number, err?: EmitError };
10
+ import { CompilerWatcher } from './watch';
11
+ import { Log } from './log';
12
+ import { CompileEmitError, CompileEmitEvent, CompileEmitter } from './types';
19
13
 
20
14
  /**
21
15
  * Compilation support
22
16
  */
23
17
  export class Compiler {
24
18
 
25
- #bootTsconfig: string;
26
- #state: CompilerState;
27
- #transformers: string[];
28
-
29
- init(manifestState: ManifestState): this {
30
- this.#state = new CompilerState(manifestState);
31
- this.#bootTsconfig = this.#state.resolveModuleFile('@travetto/compiler', 'tsconfig.trv.json');
32
-
33
- this.#transformers = this.state.modules.flatMap(
34
- x => (x.files.support ?? [])
35
- .filter(([f, type]) => type === 'ts' && f.startsWith('support/transformer.'))
36
- .map(([f]) =>
37
- path.resolve(
38
- this.#state.manifest.workspacePath,
39
- this.#state.manifest.compilerFolder,
40
- x.output,
41
- f.replace(/[.][tj]s$/, '.js')
42
- )
43
- )
44
- );
45
-
46
- return this;
19
+ /**
20
+ * Run compiler as a main entry point
21
+ */
22
+ static async main(): Promise<void> {
23
+ const [dirty, watch] = process.argv.slice(2);
24
+ const state = await CompilerState.get(RootIndex);
25
+ const dirtyFiles = (await fs.readFile(dirty, 'utf8')).split(/\n/).filter(x => !!x);
26
+ await new Compiler(state, dirtyFiles, watch === 'true').run();
27
+ process.exit(0);
47
28
  }
48
29
 
49
- get state(): CompilerState {
50
- return this.#state;
30
+ #state: CompilerState;
31
+ #dirtyFiles: string[];
32
+ #watch?: boolean;
33
+
34
+ constructor(state: CompilerState, dirtyFiles: string[], watch?: boolean) {
35
+ this.#state = state;
36
+ this.#dirtyFiles = dirtyFiles[0] === '*' ?
37
+ this.#state.getAllFiles() :
38
+ dirtyFiles.map(f => this.#state.getBySource(f)!.input);
39
+ this.#watch = watch;
51
40
  }
52
41
 
53
42
  /**
54
43
  * Watches local modules
55
44
  */
56
- async #watchLocalModules(emit: Emitter): Promise<() => Promise<void>> {
57
- const folders = this.state.modules.filter(x => x.local).map(x => x.source);
58
- const emitWithError = (file: string): void => {
59
- const err = emit(file, true);
45
+ #watchLocalModules(emit: CompileEmitter): Promise<() => Promise<void>> {
46
+ return new CompilerWatcher(this.#state).watchFiles(async file => {
47
+ const err = await emit(file, true);
60
48
  if (err) {
61
- console.error(CompilerUtil.buildTranspileError(file, err));
49
+ Log.info('Compilation Error', CompilerUtil.buildTranspileError(file, err));
62
50
  } else {
63
- console.error('Compiled', file.split('node_modules/')[1]);
51
+ Log.info(`Compiled ${file.split('node_modules/')[1]}`);
64
52
  }
65
- };
66
- const watcher = this.state.getWatcher({
67
- create: (inputFile) => emitWithError(inputFile),
68
- update: (inputFile) => emitWithError(inputFile),
69
- delete: (outputFile) => fs.unlink(outputFile).catch(() => { })
53
+ return err;
70
54
  });
71
- return CompilerUtil.fileWatcher(folders, watcher);
72
- }
73
-
74
- async createTransformerProvider(): Promise<TransformerProvider> {
75
- const { TransformerManager } = await import('@travetto/transformer');
76
- return TransformerManager.create(this.#transformers, this.state.manifest);
77
- }
78
-
79
- async writeRawFile(file: string, contents: string, mode?: string): Promise<void> {
80
- const outFile = path.resolve(
81
- this.#state.manifest.workspacePath,
82
- this.#state.manifest.outputFolder,
83
- file
84
- );
85
- await fs.mkdir(path.dirname(outFile), { recursive: true });
86
- await fs.writeFile(outFile, contents, { encoding: 'utf8', mode });
87
- }
88
-
89
- async outputInit(): Promise<void> {
90
- // Write manifest
91
- await this.writeRawFile(this.#state.manifest.manifestFile, JSON.stringify(this.state.manifest));
92
- // TODO: This needs to be isolated, just like in the bootstrap
93
- await this.writeRawFile('trv', '#!/bin/sh\nnode node_modules/@travetto/cli/support/main.cli.js $@\n', '755');
94
- await this.writeRawFile('trv.cmd', 'node node_modules/@travetto/cli/support/main.cli.js %*\n', '755');
95
55
  }
96
56
 
97
57
  /**
98
58
  * Compile in a single pass, only emitting dirty files
99
59
  */
100
- async getCompiler(): Promise<Emitter> {
60
+ async getCompiler(): Promise<CompileEmitter> {
101
61
  let program: ts.Program;
102
62
 
103
- const transformers = await this.createTransformerProvider();
104
- const options = await CompilerUtil.getCompilerOptions(
105
- path.resolve(
106
- this.#state.manifest.workspacePath,
107
- this.#state.manifest.outputFolder,
108
- ),
109
- this.#bootTsconfig,
110
- this.#state.manifest.workspacePath
111
- );
112
- const host = this.state.getCompilerHost(options);
113
-
114
- const emit = (file: string, needsNewProgram = program === undefined): EmitError | undefined => {
115
- if (needsNewProgram) {
116
- program = ts.createProgram({ rootNames: this.#state.getAllFiles(), host, options, oldProgram: program });
117
- transformers.init(program.getTypeChecker());
118
- }
63
+ const emit = async (inputFile: string, needsNewProgram = program === undefined): Promise<CompileEmitError | undefined> => {
119
64
  try {
120
- const result = program.emit(
121
- program.getSourceFile(file)!, host.writeFile, undefined, false, transformers.get()
122
- );
123
-
124
- if (result.diagnostics?.length) {
65
+ if (needsNewProgram) {
66
+ program = this.#state.createProgram(program);
67
+ }
68
+ const result = this.#state.writeInputFile(program, inputFile);
69
+ if (result?.diagnostics?.length) {
125
70
  return result.diagnostics;
126
71
  }
127
72
  } catch (err) {
@@ -139,49 +84,65 @@ export class Compiler {
139
84
  /**
140
85
  * Emit all files as a stream
141
86
  */
142
- async * emit(files: string[], emitter: Emitter): AsyncIterable<EmitEvent> {
87
+ async * emit(files: string[], emitter: CompileEmitter): AsyncIterable<CompileEmitEvent> {
143
88
  let i = 0;
144
- const manifest = this.#state.manifest;
145
89
  for (const file of files) {
146
- const err = emitter(file);
147
- const outputFile = file
148
- .replace(/[.]ts$/, '.js')
149
- .replace(manifest.compilerFolder, manifest.outputFolder);
150
- yield { file: outputFile, i: i += 1, err, total: files.length };
90
+ const err = await emitter(file);
91
+ const imp = file.replace(/.*node_modules\//, '');
92
+ yield { file: imp, i: i += 1, err, total: files.length };
151
93
  }
94
+ Log.debug(`Compiled ${i} files`);
152
95
  }
153
96
 
154
97
  /**
155
98
  * Run the compiler
156
99
  */
157
- async run(watch?: boolean): Promise<void> {
158
- await this.outputInit();
100
+ async run(): Promise<void> {
101
+ await GlobalTerminal.init();
102
+ install();
103
+
104
+ Log.debug('Compilation started');
105
+
106
+ process.on('disconnect', () => process.exit(0));
107
+
159
108
  const emitter = await this.getCompiler();
160
109
  let failed = false;
161
110
 
162
- const resolveEmittedFile = ({ file, total, i, err }: EmitEvent): TerminalProgressEvent => {
111
+ Log.debug('Compiler loaded');
112
+
113
+ const resolveEmittedFile = ({ file, total, i, err }: CompileEmitEvent): TerminalProgressEvent => {
163
114
  if (err) {
164
115
  failed = true;
165
116
  console.error(CompilerUtil.buildTranspileError(file, err));
166
117
  }
167
- return { idx: i, total, text: `Compiling [%idx/%total] -- ${file.split('node_modules/')[1]}` };
118
+ return { idx: i, total, text: `Compiling [%idx/%total] -- ${file}` };
168
119
  };
169
120
 
170
- let files = this.state.getDirtyFiles();
171
- if (!watch && !files.length) {
172
- files = this.state.getAllFiles();
173
- }
174
-
175
- if (files.length) {
176
- await GlobalTerminal.trackProgress(this.emit(files, emitter), resolveEmittedFile, { position: 'bottom' });
121
+ if (this.#dirtyFiles.length) {
122
+ await GlobalTerminal.trackProgress(this.emit(this.#dirtyFiles, emitter), resolveEmittedFile, { position: 'bottom' });
177
123
  if (failed) {
124
+ Log.debug('Compilation failed');
178
125
  process.exit(1);
126
+ } else {
127
+ Log.debug('Compilation succeeded');
179
128
  }
129
+ } else if (this.#watch) {
130
+ // Prime compiler before complete
131
+ const resolved = this.#state.getArbitraryInputFile();
132
+ await emitter(resolved, true);
180
133
  }
181
134
 
182
- if (watch) {
135
+ process.send?.('build-complete');
136
+
137
+ if (this.#watch) {
138
+ Log.info('Watch is ready');
183
139
  await this.#watchLocalModules(emitter);
184
- await new Promise(r => setTimeout(r, 1000 * 60 * 60 * 24));
140
+ const output = this.#state.resolveOutputFile('.');
141
+ for await (const _ of fs.watch(output)) {
142
+ if (!await fs.stat(output).catch(() => false)) {
143
+ process.send?.('restart');
144
+ }
145
+ }
185
146
  }
186
147
  }
187
- }
148
+ }
package/src/log.ts ADDED
@@ -0,0 +1,18 @@
1
+ import util from 'util';
2
+
3
+ import type { CompilerLogEvent } from '../support/log';
4
+
5
+ function log(level: 'info' | 'debug', message: string, ...args: unknown[]): void {
6
+ if (process.send) {
7
+ const ev: CompilerLogEvent = [level, util.format(message, ...args)];
8
+ process.send(ev);
9
+ } else {
10
+ // eslint-disable-next-line no-console
11
+ console[level](message, ...args);
12
+ }
13
+ }
14
+
15
+ export const Log = {
16
+ debug: log.bind(null, 'debug'),
17
+ info: log.bind(null, 'info')
18
+ };