├── .gitignore
├── .nvmrc
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── client.d.ts
├── client.js
├── example
├── main.js
├── main.ts
└── tsconfig.json
├── index.js
├── jest.config.js
├── package-lock.json
├── package.json
├── src
├── client
│ ├── client.ts
│ └── index.ts
├── lib
│ ├── @types
│ │ ├── fs-extra.ts
│ │ └── string-argv.d.ts
│ ├── args-manager.ts
│ ├── compiler-provider.ts
│ ├── debounce.ts
│ ├── killer.ts
│ ├── runner.ts
│ ├── stdout-manipulator.ts
│ └── tsc-watch.ts
└── test
│ ├── args-manager.test.ts
│ ├── client.test.ts
│ ├── compiler-provider.test.ts
│ ├── driver.ts
│ ├── fixtures
│ ├── failing.ts
│ └── passing.ts
│ ├── stdout-manipulator.test.ts
│ ├── test-utils.ts
│ └── tsc-watch.test.ts
├── tsc-watch-client-example.js
├── tsconfig-client.json
└── tsconfig.json
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .vscode
3 | node_modules/
4 | npm-debug.log
5 | tmp/
6 | dist/
7 | types/
8 | RELEASE_PROCESS.md
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 12.22.10
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '17'
4 | - '16'
5 | - '14'
6 | - '12'
7 | - '10'
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @gilamran/tsc-watch CHANGELOG
2 | ## v7.1.1 - 28/05/2025
3 |
4 | - feature: Added support for tsgo (native compilation))
5 |
6 | # @gilamran/tsc-watch CHANGELOG
7 | ## v7.0.0 - 25/05/2025
8 |
9 | - feature: Globally installed `tsc-watch` will use the local compiler if it exists, otherwise it will use the global `tsc` compiler.
10 | - Update existing test code to pass the new version of typescript
11 | - Update existing test code to work in Mac OS(M1 chip)
12 |
13 | # @gilamran/tsc-watch CHANGELOG
14 | ## v6.3.1 - 18/05/2025
15 |
16 | - big fix: off-by-one error tsc-watch. thanks to @rodw for the PR
17 | # @gilamran/tsc-watch CHANGELOG
18 | ## v6.3.0 - 16/04/2024
19 |
20 | - Added `--noWatch` argument
21 |
22 | ## v6.2.1 - 13/11/2024
23 | - bug fix: Fixed new version of Typescript clear codes. thanks to @staszekj
24 |
25 | ## v6.2.0 - 28/03/2024
26 | - feature: Using prepublish. thanks to @llllvvuu
27 |
28 | ## v6.1.0 - 27/03/2024
29 | - feature: --noEmit. thanks to @llllvvuu
30 |
31 | ## v6.0.5 - 27/03/2024
32 | - fix: error TS6369: Option '--build' must be the first command line argument. thanks to @llllvvuu
33 |
34 | ## v6.0.4 - 29/04/2023
35 | - Automate CRLF to LF conversion
36 |
37 | ## v6.0.3 - 29/04/2023
38 | - Fixed (Again CRLF to LF)
39 |
40 | ## v6.0.1 - 28/04/2023
41 | - Upgraded to the latest versions of dev dependencies
42 | - fixed failing test on node 18
43 |
44 | ## v6.0.0 - 12/09/2022
45 | - killing processes once, and waiting for them to complete before starting a new one
46 | - added signalEmittedFiles, thanks to @pp0rtal
47 |
48 | ## v5.0.0 - 12/03/2022
49 | - Converted to Typescript
50 | ## v5.0.3 - 07/04/2022
51 |
52 | - Fixed DOS line endings [issue](https://github.com/gilamran/tsc-watch/issues/149)) thanks to @sluukkonen
53 |
54 | ## v5.0.2 - 03/04/2022
55 |
56 | - Fixed black on black issue (Thanks to @bonjourjoel for the [issue](https://github.com/gilamran/tsc-watch/issues/142))
57 |
58 | ## v5.0.1 - 03/04/2022
59 |
60 | - bumped to version 5.0.1 to prevent dev version as the latest version
61 |
62 | ## v4.6.2 - 10/01/2022
63 |
64 | - Added `file_emitted` signal support when `--listEmittedFiles` is used [issue](https://github.com/gilamran/tsc-watch/issues/138)
65 | - Added client new event `file_emitted` with the emitted file path
66 |
67 | ## v4.6.1 - 10/3/2022
68 |
69 | - Added `--maxNodeMem` param to set manually node allocated memory [issue](https://github.com/gilamran/tsc-watch/issues/137) - (Thanks to @pp0rtal for the idea and the PR!)
70 |
71 | ## v4.6.0 - 20/12/2021
72 |
73 | - Added `silent` option - (Thanks to @axtk for the idea and @fmvilas for the PR)
74 |
75 | ## v4.5.0 - 19/8/2021
76 |
77 | - Added `onCompilationStarted` option - (Thanks to @axtk for the idea and @dko-slapdash for the PR)
78 | - Fix: enable unit tests which were turned off accidentally
79 |
80 | ## v4.4.0 - 26/5/ 2021
81 |
82 | - tsc-watch is now listening to message and reacts to them
83 |
84 | ## v4.3.1 - 26/5/2021
85 |
86 | - Fix: compiler resolving - (Thanks to @merceyz for the PR)
87 |
88 | ## v4.2.9 - 23/6/2020
89 |
90 | - Fix: upgrade cross-spawn and strip-ansi (node 8+) - (Thanks to @FauxFaux for the PR)
91 |
92 | ## v4.2.8 - 23/5/2020
93 |
94 | - Fix: spawn compiler using node, this fixes issues with yarn v2 (pnp) - (Thanks to @merceyz)
95 |
96 | ## v4.2.6 - 18/5/2020
97 |
98 | - Using number 15 instead of SIGTERM to support POSIX standard - (Thanks to @Asarew)
99 |
100 | ## v4.2.0 - 29/2/2020
101 |
102 | - Using readline instead of raw stdout buffer - (Thanks to @Janpot for the idea)
103 |
104 | ## v4.1.0 - 22/1/2020
105 |
106 | - Added the `onCompilationComplete` option - (Thanks to @ackvf for the idea)
107 |
108 | ## v4.0.0 - 19/9/2019
109 |
110 | - Terminating previous processes is now done with `SIGTERM` instead of `SIGUSR2` - (Thanks to @zontarian)
111 |
112 | ## v3.0.0 - 9/9/2019
113 |
114 | - onSuccess will run on EVERY successful compilation, also on the first success - (Thanks to @mchl-hub for the idea)
115 |
116 | ## v2.2.1 - 19/5/2019
117 |
118 | - Force kill when on windows - (Thanks to @hwwi)
119 |
120 | ## v2.2.0 - 13/5/2019
121 |
122 | - Waiting for all the child processes to showdown before closing - (Thanks to @MartinLoeper)
123 |
124 | ## v2.1.0 - 12/2/2019
125 |
126 | - Exporting TscWatchClient for multiple instance of `tsc-watch` - (Thanks to @pronebird)
127 |
128 | ## v2.0.0 - 12/2/2019
129 |
130 | - As many users requested, from now on `--onFirstSuccess` process will not get killed, only when tsc-watch is killed. (Based on @amir-arad's PR)
131 | This version fixes Issue #20, #21 and #50.
132 |
133 | ## v1.1.37 - 8/2/2019
134 |
135 | - Fixed coloring issues
136 |
137 | ## v1.1.36 - 31/1/2019
138 |
139 | - `--watch` will be added to the end of the arguments (Thanks to @barkayal)
140 |
141 | ## v1.1.35 - 28/1/2019
142 |
143 | - Clean code
144 |
145 | ## v1.1.32 - 27/11/2018
146 |
147 | - Added --noClear command to prevent clearing the screen after each compilation
148 |
149 | ## v1.0.32 - 27/11/2018
150 |
151 | - Removed chalk dependency - (Thanks to @frank-orellana)
152 |
153 | ## v1.0.31 - 27/11/2018
154 |
155 | - Upgraded ps-tree, preventing flatmap-stream attack (Thanks to @jeremyhon)
156 |
157 | ## v1.0.30 - 10/10/2018
158 |
159 | - Added custom compiler ability (Thanks to @sosoba for the PR)
160 |
161 | ## v1.0.29 - 25/09/2018
162 |
163 | - Fixed regression bug (Thanks to @onehorsetown)
164 |
165 | ## v1.0.28 - 25/09/2018
166 |
167 | - Fixed command args extraction (Thanks to @mscharley)
168 |
169 | ## v1.0.27 - 27/08/2018
170 |
171 | - Fixed process termination (Thanks to @igrayson)
172 |
173 | ## v1.0.26 - 20/07/2018
174 |
175 | - Fixed several issues with hadErrors check (Thanks to @tombousso)
176 |
177 | ## v1.0.24 - 20/07/2018
178 |
179 | - Killing onFail and onSuccess after compilation
180 |
181 | ## v1.0.23 - 1/06/2018
182 |
183 | - Typescript as a peer dependency (Thanks to @asztal)
184 |
185 | ## v1.0.22 - 1/06/2018
186 |
187 | - Added support for Typescript 2.9+
188 |
189 | ## v1.0.21 - 16/04/2018
190 |
191 | - Removed `--noClearScreen` argument, as typescript added `--preserveWatchOutput`
192 |
193 | ## v1.0.20 - 16/04/2018
194 |
195 | - Fixed ignoring the last arg
196 | - Added `--noColors` argument
197 | - Added `--noClearScreen` argument
198 |
199 | ## v1.0.19 - 8/04/2018
200 |
201 | - Fixed passing compilation when using --pretty param (Thanks to @tomaba)
202 |
203 | ## v1.0.17 - 27/01/2018
204 |
205 | - Terminating typescript on SIGTERM event (Thanks to @amir-arad)
206 |
207 | ## v1.0.16 - 27/01/2018
208 |
209 | - Fixed stdout coloring issues on Mac (Thanks to @jonaskello)
210 |
211 | ## v1.0.15 - 18/01/2018
212 |
213 | - Added `--onFailure` argument
214 |
215 | ## v1.0.14 - 18/01/2018
216 |
217 | - Fixed Windows newline issue
218 |
219 | ## v1.0.9 - 5/12/2017
220 |
221 | - Add CHANGELOG
222 | - Fix typo in package description
223 | - Upgraded chalk version
224 | - Change main to `index.js`
225 | - Removed `yarn.lock` & `package-lock.json`
226 | - Made `index.js` executable
227 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright (c) Gil Amran
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://travis-ci.com/gilamran/tsc-watch)
2 |
3 | # The nodemon for TypeScript
4 |
5 | `tsc-watch` starts the installed TypeScript compiler (`tsc`) with `--watch` parameter, with the ability to react to compilation status.
6 | `tsc-watch` was created to allow an easy dev process with TypeScript. Commonly used to restart a node server, similar to nodemon but for TypeScript.
7 |
8 | **Anything that you can do with `tsc` you can do with `tsc-watch`, the only difference is that `tsc-watch` can react to compilation status.**
9 |
10 | | Argument | Description |
11 | | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
12 | | `--onSuccess COMMAND` | Executes `COMMAND` on **every successful** compilation. |
13 | | `--onFirstSuccess COMMAND` | Executes `COMMAND` on the **first successful** compilation. |
14 | | `--onEmit COMMAND` | Executes debounced `COMMAND` on **every emitted file**, ignoring unchanged files and disregards compilation success or failure. |
15 | | `--onEmitDebounceMs DELAY` | Delay by which to debounce `--onEmit` (default: 300). |
16 | | `--onFailure COMMAND` | Executes `COMMAND` on **every failed** compilation. |
17 | | `--onCompilationStarted COMMAND` | Executes `COMMAND` on **every compilation start** event (initial and incremental). |
18 | | `--onCompilationComplete COMMAND` | Executes `COMMAND` on **every successful or failed** compilation. |
19 | | `--maxNodeMem` | Calls `node` with a specific memory limit `max_old_space_size`, to use if your project needs more memory. |
20 | | `--noColors` | By default tsc-watch adds colors the output with green
on success, and in red on failure.
Add this argument to prevent that. |
21 | | `--noWatch` | By default tsc-watch adds --watch. using this to prevent watch. |
22 | | `--noClear` | In watch mode the `tsc` compiler clears the screen before reporting
Add this argument to prevent that. |
23 | | `--signalEmittedFiles` | Will run `tsc` compiler with `--listEmittedFiles`, but hiding TSFILE lines. Use it to enable `file_emitted` event, while keeping tsc stdout silent. |
24 | | `--silent` | Do not print any messages on stdout. |
25 | | `--compiler PATH` | The `PATH` will be used instead of typescript compiler.
Default is `typescript/bin/tsc` |
26 |
27 | Notes:
28 |
29 | - That all the above `COMMAND`s will be killed on process exit. (Using `SIGTERM`)
30 |
31 | - A `COMMAND` is a single command and not multi command like `script1.sh && script2.sh`
32 |
33 | - Any child process (`COMMAND`) will be terminated before creating a new one.
34 |
35 | - To prevent the watch, use `--noWatch` or pass `--watch false` as a parameter.
36 |
37 | - tsc-watch supports tsgo (native compilation), just select the compiler path. `tsc-watch --compiler .bin/tsgo`
38 |
39 | ## Install
40 |
41 | ```sh
42 | npm install tsc-watch --save-dev
43 | ```
44 |
45 | ```sh
46 | ## for command-line usage
47 | npm install -g typescript tsc-watch
48 | ```
49 |
50 | ## Usage
51 |
52 | ### From Command-Line
53 |
54 | ```sh
55 | ## Watching a project (with tsconfig.json)
56 | tsc-watch --onSuccess "node ./dist/server.js"
57 |
58 | ## Beep on failure
59 | tsc-watch --onFailure "echo Beep! Compilation Failed"
60 |
61 | ## Watching a single file
62 | tsc-watch server.ts --outDir ./dist --onSuccess "node ./dist/server.js"
63 |
64 | ## Custom compiler
65 | tsc-watch --onSuccess "node ./dist/server.js" --compiler my-typescript/bin/tsc
66 | ```
67 |
68 | ### From npm script
69 |
70 | ```
71 | "dev-server": "tsc-watch --noClear -p ./src/tsconfig.json --onSuccess \"node ./dist/server.js\"",
72 | ```
73 |
74 | ### From javascript
75 |
76 | You can see a detailed example [here](https://github.com/gilamran/tsc-watch/blob/master/tsc-watch-client-example.js)
77 |
78 | The client is implemented as an instance of `Node.JS`'s `EventEmitter`, with the following events:
79 |
80 | - `started` - Emitted upon the compilation start (initial or incremental).
81 | - `first_success` - Emitted upon first successful compilation.
82 | - `subsequent_success` - Emitted upon every subsequent successful compilation.
83 | - `compile_errors` - Emitted upon every failing compilation.
84 | - `file_emitted` - Emitted upon every file transpiled if `--listEmittedFiles` is used.
85 |
86 | Once subscribed to the relevant events, start the client by running `watch.start()`
87 |
88 | To kill the client, run `watch.kill()`
89 |
90 | Example usage:
91 |
92 | ```javascript
93 | // Using CommonJS:
94 | const { TscWatchClient } = require('tsc-watch/client');
95 | // Using ES6 import:
96 | import { TscWatchClient } from 'tsc-watch/client';
97 |
98 | const watch = new TscWatchClient();
99 |
100 | watch.on('started', () => {
101 | console.log('Compilation started');
102 | });
103 |
104 | watch.on('first_success', () => {
105 | console.log('First success!');
106 | });
107 |
108 | watch.on('success', () => {
109 | // Your code goes here...
110 | });
111 |
112 | watch.on('compile_errors', () => {
113 | // Your code goes here...
114 | });
115 |
116 | watch.start('--project', '.');
117 |
118 | try {
119 | // do something...
120 | } catch (e) {
121 | watch.kill(); // Fatal error, kill the compiler instance.
122 | }
123 | ```
124 |
125 | Notes:
126 |
127 | - The (`onSuccess`) `COMMAND` will not run if the compilation failed.
128 | - The (`onEmit`) `COMMAND` will not run if the compilation succeeded with no changed files, unless it is the first success.
129 | - The (`onEmit`) `COMMAND` will run even if the compilation failed, but emitted changed files.
130 | - The (`onEmit`) `COMMAND` will not run 100 times for 100 files, due to `--onEmitDebounce`
131 | - The (`onEmit`) `COMMAND` is not cancelling the `onSuccess`/`onFirstSuccess`/`onFailure`/`onCompilationComplete`/`onCompilationStarted` commands and vice versa.
132 | - `tsc-watch` is using the currently installed TypeScript compiler.
133 | - `tsc-watch` is not changing the compiler, just adds the new arguments, compilation is the same, and all other arguments are the same.
134 | - if `tsc-watch` was installed globally, it will use the local compiler if it exists, otherwise it will use the global `tsc` compiler.
135 |
--------------------------------------------------------------------------------
/client.d.ts:
--------------------------------------------------------------------------------
1 | export * from "./dist/client"
2 |
--------------------------------------------------------------------------------
/client.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/client');
2 |
--------------------------------------------------------------------------------
/example/main.js:
--------------------------------------------------------------------------------
1 | console.log('Generating random number every sec');
2 | setInterval(() => console.log(Math.random()), 1000);
3 |
--------------------------------------------------------------------------------
/example/main.ts:
--------------------------------------------------------------------------------
1 | console.log('Generating random number every sec');
2 | setInterval(() => console.log(Math.random()), 1000);
3 |
4 |
--------------------------------------------------------------------------------
/example/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "noImplicitAny": false,
6 | "sourceMap": false
7 | },
8 | "files": [
9 | "main.ts"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require('./dist/lib/tsc-watch');
4 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import('ts-jest/dist/types').JestConfigWithTsJest} */
2 | module.exports = {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | testMatch: ["**/src/test/*.test.ts"],
6 | testTimeout: 30000,
7 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tsc-watch",
3 | "version": "7.1.1",
4 | "description": "The TypeScript compiler with onSuccess command",
5 | "scripts": {
6 | "clean": "rimraf dist && rimraf tmp",
7 | "prepublishOnly": "npm run build && crlf --set=LF index.js client.js dist/**/*",
8 | "test": "npm run build && jest --verbose --runInBand",
9 | "build": "npm run clean && npm run build-lib && npm run build-client",
10 | "build-lib": "tsc -p tsconfig.json",
11 | "build-client": "tsc -p tsconfig-client.json",
12 | "watch-lib": "tsc -p tsconfig.json --watch"
13 | },
14 | "bin": {
15 | "tsc-watch": "dist/lib/tsc-watch.js"
16 | },
17 | "files": [
18 | "dist/**/*",
19 | "index.js",
20 | "client.js",
21 | "client.d.ts"
22 | ],
23 | "repository": {
24 | "type": "git",
25 | "url": "git+ssh://git@github.com/gilamran/tsc-watch.git"
26 | },
27 | "keywords": [
28 | "TypeScript"
29 | ],
30 | "main": "./dist/client/index.js",
31 | "types": "./dist/client/index.d.ts",
32 | "engines": {
33 | "node": ">=12.12.0"
34 | },
35 | "author": "Gil Amran",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/gilamran/tsc-watch/issues"
39 | },
40 | "homepage": "https://github.com/gilamran/tsc-watch#readme",
41 | "prettier": {
42 | "trailingComma": "all",
43 | "tabWidth": 2,
44 | "semi": true,
45 | "singleQuote": true,
46 | "printWidth": 100
47 | },
48 | "dependencies": {
49 | "cross-spawn": "^7.0.6",
50 | "node-cleanup": "^2.1.2",
51 | "ps-tree": "^1.2.0",
52 | "string-argv": "^0.3.2"
53 | },
54 | "peerDependencies": {
55 | "typescript": "*"
56 | },
57 | "devDependencies": {
58 | "@types/cross-spawn": "^6.0.6",
59 | "@types/jest": "^29.5.14",
60 | "@types/node": "^12.12.0",
61 | "@types/node-cleanup": "^2.1.5",
62 | "@types/ps-tree": "^1.1.6",
63 | "crlf": "^1.1.1",
64 | "fs-extra": "^11.3.0",
65 | "jest": "^29.7.0",
66 | "rimraf": "^6.0.1",
67 | "ts-jest": "^29.3.4",
68 | "typescript": "^5.8.3"
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/client/client.ts:
--------------------------------------------------------------------------------
1 | import * as path from 'path';
2 | import { ChildProcess, fork, ForkOptions } from 'child_process';
3 | import { EventEmitter } from 'events';
4 |
5 | const tscWatchLibPath = path.join(__dirname, '..', 'lib', 'tsc-watch');
6 |
7 | export class TscWatchClient extends EventEmitter {
8 | private tsc: ChildProcess | undefined;
9 |
10 | constructor(private tscWatchPath = tscWatchLibPath) {
11 | super();
12 | }
13 |
14 | start(...args: string[]) {
15 | const options: ForkOptions = { stdio: 'inherit' };
16 | this.tsc = fork(this.tscWatchPath, args, options);
17 | this.tsc.on('message', (msg: string) => {
18 | this.emit(...deserializeTscMessage(msg));
19 | });
20 | this.tsc.on('exit', (code: number, signal: string) => {
21 | this.emit('exit', code, signal);
22 | });
23 | }
24 |
25 | kill() {
26 | if (this.tsc && this.tsc.kill) {
27 | this.tsc.kill();
28 | }
29 | this.removeAllListeners();
30 | }
31 |
32 | runOnCompilationStartedCommand() {
33 | if (this.tsc) {
34 | this.tsc.send('run-on-compilation-started-command');
35 | }
36 | }
37 |
38 | runOnCompilationCompleteCommand() {
39 | if (this.tsc) {
40 | this.tsc.send('run-on-compilation-complete-command');
41 | }
42 | }
43 |
44 | runOnFirstSuccessCommand() {
45 | if (this.tsc) {
46 | this.tsc.send('run-on-first-success-command');
47 | }
48 | }
49 |
50 | runOnFailureCommand() {
51 | if (this.tsc) {
52 | this.tsc.send('run-on-failure-command');
53 | }
54 | }
55 |
56 | runOnSuccessCommand() {
57 | if (this.tsc) {
58 | this.tsc.send('run-on-success-command');
59 | }
60 | }
61 |
62 | runOnEmitCommand() {
63 | if (this.tsc) {
64 | this.tsc.send('run-on-emit-command');
65 | }
66 | }
67 | }
68 |
69 | function deserializeTscMessage(strMsg: string): [string, string?] {
70 | const indexOfSeparator = strMsg.indexOf(':');
71 | if (indexOfSeparator === -1) {
72 | return [strMsg];
73 | }
74 |
75 | return [strMsg.substring(0, indexOfSeparator), strMsg.substring(indexOfSeparator + 1)];
76 | }
77 |
--------------------------------------------------------------------------------
/src/client/index.ts:
--------------------------------------------------------------------------------
1 | export * from './client';
--------------------------------------------------------------------------------
/src/lib/@types/fs-extra.ts:
--------------------------------------------------------------------------------
1 | declare module 'fs-extra';
--------------------------------------------------------------------------------
/src/lib/@types/string-argv.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'string-argv';
--------------------------------------------------------------------------------
/src/lib/args-manager.ts:
--------------------------------------------------------------------------------
1 | import { getCompilerPath } from "./compiler-provider";
2 |
3 | function removeRunnerArgs(args: string[]): string[] {
4 | return args.splice(2); // removing "node tsc-watch.js"
5 | }
6 |
7 | function getCommandIdx(args: string[], command: string): number {
8 | const lowerCasedCommand = command.toLowerCase();
9 | return args.map((arg) => arg.toLowerCase()).indexOf(lowerCasedCommand);
10 | }
11 |
12 | export function isCommandExist(args: string[], command: string): boolean {
13 | return getCommandIdx(args, command) >= 0;
14 | }
15 |
16 | export function hasWatchCommand(args: string[]): boolean {
17 | return isCommandExist(args, '-w') || isCommandExist(args, '--watch');
18 | }
19 |
20 | function forceWatch(args: string[]): string[] {
21 | if (!hasWatchCommand(args)) {
22 | args.push('--watch');
23 | }
24 |
25 | return args;
26 | }
27 |
28 | function extractCommandWithValue(args: string[], command: string): string | null {
29 | let commandValue = null;
30 | let commandIdx = getCommandIdx(args, command);
31 | if (commandIdx > -1) {
32 | commandValue = args[commandIdx + 1];
33 | args.splice(commandIdx, 2);
34 | }
35 | return commandValue;
36 | }
37 |
38 | function extractCommand(args: string[], command: string): boolean {
39 | let commandIdx = getCommandIdx(args, command);
40 | if (commandIdx > -1) {
41 | args.splice(commandIdx, 1);
42 | return true;
43 | }
44 | return false;
45 | }
46 |
47 | export function extractArgs(inputArgs: string[]) {
48 | const cleanArgs = removeRunnerArgs(inputArgs);
49 | const noWatch = extractCommand(cleanArgs, '--noWatch');
50 | const args = noWatch ? cleanArgs : forceWatch(cleanArgs);
51 |
52 | const onFirstSuccessCommand = extractCommandWithValue(args, '--onFirstSuccess');
53 | const onSuccessCommand = extractCommandWithValue(args, '--onSuccess');
54 | const onFailureCommand = extractCommandWithValue(args, '--onFailure');
55 | const onEmitCommand = extractCommandWithValue(args, '--onEmit');
56 | const onEmitDebounceMs = Number(extractCommandWithValue(args, '--onEmitDebounceMs')) || 300;
57 | const onCompilationStarted = extractCommandWithValue(args, '--onCompilationStarted');
58 | const onCompilationComplete = extractCommandWithValue(args, '--onCompilationComplete');
59 | const maxNodeMem = extractCommandWithValue(args, '--maxNodeMem');
60 | const noColors = extractCommand(args, '--noColors');
61 | const noClear = extractCommand(args, '--noClear');
62 | const silent = extractCommand(args, '--silent');
63 | const signalEmittedFiles = extractCommand(args, '--signalEmittedFiles');
64 | const requestedToListEmittedFiles = extractCommand(args, '--listEmittedFiles');
65 | const compiler = getCompilerPath(extractCommandWithValue(args, '--compiler'));
66 |
67 |
68 | if (signalEmittedFiles || requestedToListEmittedFiles) {
69 | if (args[0] === '--build' || args[0] === '-b') {
70 | // TS6369: Option '--build' must be the first command line argument.
71 | args.splice(1, 0, '--listEmittedFiles');
72 | } else {
73 | args.unshift('--listEmittedFiles');
74 | }
75 | }
76 |
77 | return {
78 | onFirstSuccessCommand,
79 | onSuccessCommand,
80 | onFailureCommand,
81 | onEmitCommand,
82 | onEmitDebounceMs,
83 | onCompilationStarted,
84 | onCompilationComplete,
85 | maxNodeMem,
86 | noColors,
87 | noClear,
88 | requestedToListEmittedFiles,
89 | signalEmittedFiles,
90 | silent,
91 | compiler,
92 | args,
93 | };
94 | }
95 |
--------------------------------------------------------------------------------
/src/lib/compiler-provider.ts:
--------------------------------------------------------------------------------
1 | export function getCompilerPath(compilerArg: string | null, resolver: NodeRequire['resolve'] = require.resolve): string {
2 | if (!compilerArg) {
3 | compilerArg = 'typescript/bin/tsc';
4 | }
5 |
6 | try {
7 | return resolver(compilerArg, { paths: [process.cwd()] });
8 | } catch (e) {
9 | // Local compiler not found, ignore and try global compiler
10 | }
11 |
12 | try {
13 | return resolver(compilerArg);
14 | } catch (e: any) {
15 | if (e.code === 'MODULE_NOT_FOUND') {
16 | console.error(e.message);
17 | process.exit(9);
18 | }
19 | throw e;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/lib/debounce.ts:
--------------------------------------------------------------------------------
1 | export function debounce) => void>(this: ThisParameterType, fn: T, delay = 300) {
2 | let timer: ReturnType | undefined
3 | return (...args: Parameters) => {
4 | timer && clearTimeout(timer)
5 | timer = setTimeout(() => fn.apply(this, args), delay)
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/lib/killer.ts:
--------------------------------------------------------------------------------
1 | import psTree from 'ps-tree';
2 | import spawn from 'cross-spawn';
3 | import { ChildProcess, exec } from 'child_process';
4 |
5 | let KILL_SIGNAL = '15'; // SIGTERM
6 | let hasPS = true;
7 |
8 | const isWindows = process.platform === 'win32';
9 |
10 | // discover if the OS has `ps`, and therefore can use psTree
11 | exec('ps', function (error) {
12 | if (error) {
13 | hasPS = false;
14 | }
15 | });
16 |
17 | export function kill(child: ChildProcess): Promise {
18 | return new Promise((resolve) => {
19 | if (isWindows) {
20 | exec(`taskkill /pid ${child.pid} /T /F`, () => resolve());
21 | } else {
22 | if (hasPS) {
23 | psTree(child.pid, (err, kids) => {
24 | const kidsPIDs = kids.map((p) => p.PID);
25 | const args = [`-${KILL_SIGNAL}`, child.pid.toString(), ...kidsPIDs];
26 | spawn('kill', args).on('close', resolve);
27 | });
28 | } else {
29 | exec(`kill -${KILL_SIGNAL} ${child.pid}`, () => resolve());
30 | }
31 | }
32 | });
33 | }
34 |
--------------------------------------------------------------------------------
/src/lib/runner.ts:
--------------------------------------------------------------------------------
1 | import { ChildProcess } from 'child_process';
2 | import spawn from 'cross-spawn';
3 | import stringArgv from 'string-argv';
4 | import { kill } from './killer';
5 |
6 | function runCommand(fullCommand: string): ChildProcess {
7 | const parts: string[] = stringArgv(fullCommand);
8 | const exec = parts[0];
9 | const args = parts.splice(1);
10 | return spawn(exec, args, {
11 | stdio: 'inherit',
12 | });
13 | }
14 |
15 | export function run(command: string): () => Promise {
16 | const process = runCommand(command);
17 | const exitPromise = new Promise((resolve) => process.on('exit', resolve));
18 |
19 | return () => Promise.all([kill(process), exitPromise]);
20 | }
21 |
--------------------------------------------------------------------------------
/src/lib/stdout-manipulator.ts:
--------------------------------------------------------------------------------
1 | const ANSI_REGEX = new RegExp(
2 | '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))',
3 | 'g',
4 | );
5 | const stripAnsi = (str: string) => str.replace(ANSI_REGEX, '');
6 |
7 | const tscUsageSyntaxRegex = / -w, --watch.*Watch input files\./;
8 | const typescriptPrettyErrorRegex = /:\d+:\d+ \- error TS\d+: /;
9 | const typescriptErrorRegex = /\(\d+,\d+\): error TS\d+: /;
10 | const typescriptEmittedFileRegex = /(TSFILE:)\s*(.*)/;
11 |
12 | // errors
13 | const compilationCompleteWithErrorRegex =
14 | / Found [^0][0-9]* error[s]?\. Watching for file changes\./;
15 | const nativeCompilationCompleteWithErrorRegex = /Found [^0]?\d* error[s]? in /;
16 |
17 | // no errors
18 | const compilationCompleteWithoutErrorRegex = / Found 0 errors\. Watching for file changes\./;
19 |
20 | // compilation started
21 | const compilationStartedRegex =
22 | /( Starting compilation in watch mode\.\.\.| File change detected\. Starting incremental compilation\.\.\.)/;
23 |
24 | const nativeCompilationStartedRegex = /build starting at /;
25 |
26 | // compilation complete
27 | const compilationCompleteRegex =
28 | /( Compilation complete\. Watching for file changes\.| Found \d+ error[s]?\. Watching for file changes\.)/;
29 |
30 | const nativeCompilationCompleteRegex = /build finished in /;
31 |
32 | const newAdditionToSyntax = [
33 | ' -w, --watch Watch input files. [on by default, use --noWatch to disable]',
34 | ' --onSuccess COMMAND Executes `COMMAND` on **every successful** compilation.',
35 | ' --onFirstSuccess COMMAND Executes `COMMAND` on the **first successful** compilation.',
36 | ' --onFailure COMMAND Executes `COMMAND` on **every failed** compilation.',
37 | ' --onEmit COMMAND Executes debounced `COMMAND` on **every emitted file**, ignoring unchanged files and disregards compilation success or failure.',
38 | ' --onEmitDebounceMs DELAY Delay by which to debounce `--onEmit` (default: 300).',
39 | ' --onCompilationStarted COMMAND Executes `COMMAND` on **every compilation start** event.',
40 | ' --onCompilationComplete COMMAND Executes `COMMAND` on **every successful or failed** compilation.',
41 | ' --noColors Removes the red/green colors from the compiler output',
42 | ' --noClear Prevents the compiler from clearing the screen',
43 | ' --compiler PATH The PATH will be used instead of typescript compiler. Defaults typescript/bin/tsc.',
44 | ].join('\n');
45 |
46 | function color(line: string, noClear: boolean = false): string {
47 | // coloring errors:
48 | line = line.replace(typescriptErrorRegex, (m) => `\u001B[36m${m}\u001B[39m`); // Cyan
49 | line = line.replace(typescriptPrettyErrorRegex, (m) => `\u001B[36m${m}\u001B[39m`); // Cyan
50 |
51 | // completed with error:
52 | line = line.replace(compilationCompleteWithErrorRegex, (m) => `\u001B[31m${m}\u001B[39m`); // Red
53 | line = line.replace(nativeCompilationCompleteWithErrorRegex, (m) => `\u001B[31m${m}\u001B[39m`); // Red
54 |
55 | // completed without error:
56 | line = line.replace(compilationCompleteWithoutErrorRegex, (m) => `\u001B[32m${m}\u001B[39m`); // Green
57 |
58 | // usage
59 | line = line.replace(tscUsageSyntaxRegex, (m) => `\u001B[33m${m}\u001B[39m`); // Yellow
60 |
61 | if (noClear && compilationStartedRegex.test(line)) {
62 | return '\n\n----------------------\n' + line;
63 | }
64 |
65 | return line;
66 | }
67 |
68 | type TPrintParams = {
69 | noColors?: boolean;
70 | noClear?: boolean;
71 | requestedToListEmittedFiles?: boolean;
72 | signalEmittedFiles?: boolean;
73 | };
74 |
75 | export function print(
76 | line: string,
77 | {
78 | noColors = false,
79 | noClear = false,
80 | requestedToListEmittedFiles = false,
81 | signalEmittedFiles = false,
82 | }: TPrintParams = {},
83 | ): void {
84 | if (signalEmittedFiles && !requestedToListEmittedFiles && line.startsWith('TSFILE:')) {
85 | return;
86 | }
87 |
88 | console.log(noColors ? line : color(line, noClear));
89 | }
90 |
91 | export function deleteClear(line: string): string {
92 | // '\x1bc11:40:16 - Starting compilation in watch mode...'
93 | // '\x1b[2J\x1b[3J\x1b[H11:33:28 - Starting compilation in watch mode...'
94 | const result = line
95 | .replace(/^\x1b\[2J/, '')
96 | .replace(/^\x1b\[3J/, '')
97 | .replace(/^\x1b\[H/, '')
98 | .replace(/^\x1bc/, '');
99 | return result;
100 | }
101 |
102 | export function manipulate(line: string): string {
103 | return line.replace(tscUsageSyntaxRegex, newAdditionToSyntax);
104 | }
105 |
106 | export function detectState(line: string) {
107 | const clearLine = stripAnsi(line);
108 | const compilationStarted =
109 | compilationStartedRegex.test(clearLine) || nativeCompilationStartedRegex.test(clearLine);
110 |
111 | const compilationError =
112 | compilationCompleteWithErrorRegex.test(clearLine) ||
113 | nativeCompilationCompleteWithErrorRegex.test(clearLine) ||
114 | typescriptErrorRegex.test(clearLine) ||
115 | typescriptPrettyErrorRegex.test(clearLine);
116 |
117 | const compilationComplete =
118 | compilationCompleteRegex.test(clearLine) || nativeCompilationCompleteRegex.test(clearLine);
119 | const fileEmittedExec = typescriptEmittedFileRegex.exec(clearLine);
120 | const fileEmitted = fileEmittedExec !== null ? fileEmittedExec[2] : null; // if the regex is not null it will return an array with 3 elements
121 |
122 | return {
123 | compilationStarted,
124 | compilationError,
125 | compilationComplete,
126 | fileEmitted,
127 | };
128 | }
129 |
--------------------------------------------------------------------------------
/src/lib/tsc-watch.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | import nodeCleanup, { uninstall } from 'node-cleanup';
4 | import spawn from 'cross-spawn';
5 | import { run } from './runner';
6 | import { extractArgs } from './args-manager';
7 | import { debounce } from './debounce';
8 | import { manipulate, detectState, deleteClear, print } from './stdout-manipulator';
9 | import { createInterface } from 'readline';
10 | import { ChildProcess } from 'child_process';
11 |
12 | let firstTime = true;
13 | let firstSuccessKiller: (() => Promise) | null = null;
14 | let successKiller: (() => Promise) | null = null;
15 | let failureKiller: (() => Promise) | null = null;
16 | let emitKiller: (() => Promise) | null = null;
17 | let compilationStartedKiller: (() => Promise) | null = null;
18 | let compilationCompleteKiller: (() => Promise) | null = null;
19 |
20 | const {
21 | onFirstSuccessCommand,
22 | onSuccessCommand,
23 | onFailureCommand,
24 | onEmitCommand,
25 | onEmitDebounceMs,
26 | onCompilationStarted,
27 | onCompilationComplete,
28 | maxNodeMem,
29 | noColors,
30 | noClear,
31 | requestedToListEmittedFiles,
32 | signalEmittedFiles,
33 | silent,
34 | compiler,
35 | args,
36 | } = extractArgs(process.argv);
37 |
38 | let runningKillProcessesPromise: Promise | null = null;
39 | function killProcesses(currentCompilationId: number, killAll: boolean): Promise {
40 | if (runningKillProcessesPromise) {
41 | return runningKillProcessesPromise.then(() => currentCompilationId);
42 | }
43 |
44 | const promisesToWaitFor: Promise[] = [];
45 | if (killAll && firstSuccessKiller) {
46 | promisesToWaitFor.push(firstSuccessKiller());
47 | firstSuccessKiller = null;
48 | }
49 |
50 | if (successKiller) {
51 | promisesToWaitFor.push(successKiller());
52 | successKiller = null;
53 | }
54 |
55 | if (failureKiller) {
56 | promisesToWaitFor.push(failureKiller());
57 | failureKiller = null;
58 | }
59 |
60 | if (compilationStartedKiller) {
61 | promisesToWaitFor.push(compilationStartedKiller());
62 | compilationStartedKiller = null;
63 | }
64 |
65 | if (compilationCompleteKiller) {
66 | promisesToWaitFor.push(compilationCompleteKiller());
67 | compilationCompleteKiller = null;
68 | }
69 |
70 | runningKillProcessesPromise = Promise.all(promisesToWaitFor).then(() => {
71 | runningKillProcessesPromise = null;
72 | return currentCompilationId;
73 | });
74 |
75 | return runningKillProcessesPromise;
76 | }
77 |
78 | let runningKillEmitProcessesPromise: Promise | null = null;
79 | // The same as `killProcesses`, but we separate it to avoid canceling each other
80 | function killEmitProcesses(currentEmitId: number): Promise {
81 | if (runningKillEmitProcessesPromise) {
82 | return runningKillEmitProcessesPromise.then(() => currentEmitId);
83 | }
84 |
85 | let emitKilled = Promise.resolve();
86 | if (emitKiller) {
87 | emitKilled = emitKiller();
88 | emitKiller = null;
89 | }
90 |
91 | runningKillEmitProcessesPromise = emitKilled.then(() => {
92 | runningKillEmitProcessesPromise = null;
93 | return currentEmitId;
94 | });
95 |
96 | return runningKillEmitProcessesPromise;
97 | }
98 |
99 | function runOnCompilationStarted(): void {
100 | if (onCompilationStarted) {
101 | compilationStartedKiller = run(onCompilationStarted);
102 | }
103 | }
104 |
105 | function runOnCompilationComplete(): void {
106 | if (onCompilationComplete) {
107 | compilationCompleteKiller = run(onCompilationComplete);
108 | }
109 | }
110 |
111 | function runOnFailureCommand(): void {
112 | if (onFailureCommand) {
113 | failureKiller = run(onFailureCommand);
114 | }
115 | }
116 |
117 | function runOnFirstSuccessCommand(): void {
118 | if (onFirstSuccessCommand) {
119 | firstSuccessKiller = run(onFirstSuccessCommand);
120 | }
121 | }
122 |
123 | function runOnSuccessCommand(): void {
124 | if (onSuccessCommand) {
125 | successKiller = run(onSuccessCommand);
126 | }
127 | }
128 |
129 | const debouncedEmit = onEmitCommand
130 | ? debounce(() => { emitKiller = run(onEmitCommand) }, onEmitDebounceMs)
131 | : undefined;
132 |
133 | function runOnEmitCommand(): void {
134 | debouncedEmit?.();
135 | }
136 |
137 | interface INodeSettings {
138 | maxNodeMem: string | null;
139 | }
140 |
141 | function spawnTsc({ maxNodeMem }: INodeSettings, args: string[]): ChildProcess {
142 | const nodeArgs = [
143 | ...((maxNodeMem) ? [`--max_old_space_size=${maxNodeMem}`] : []),
144 | compiler,
145 | ...args
146 | ];
147 |
148 | return spawn('node', nodeArgs);
149 | }
150 |
151 | function echoExit(code: number | null, signal: string | null) {
152 | if (signal !== null) {
153 | process.kill(process.pid, signal);
154 | }
155 | }
156 |
157 | let compilationErrorSinceStart = false;
158 | const tscProcess = spawnTsc({ maxNodeMem }, args);
159 | if (!tscProcess.stdout) {
160 | throw new Error('Unable to read Typescript stdout');
161 | }
162 | if (!tscProcess.stderr) {
163 | throw new Error('Unable to read Typescript stderr');
164 | }
165 |
166 | tscProcess.on('exit', echoExit);
167 | tscProcess.stderr.pipe(process.stderr);
168 |
169 | const rl = createInterface({ input: tscProcess.stdout });
170 |
171 | let compilationId = 0;
172 | let emitId = 0;
173 |
174 | function triggerOnEmit() {
175 | if (onEmitCommand) {
176 | killEmitProcesses(++emitId).then((previousEmitId) => previousEmitId === emitId && runOnEmitCommand());
177 | }
178 | }
179 |
180 | rl.on('line', function (input) {
181 | if (noClear) {
182 | input = deleteClear(input);
183 | }
184 |
185 | const line = manipulate(input);
186 | if (!silent) {
187 | print(line, { noColors, noClear, signalEmittedFiles, requestedToListEmittedFiles });
188 | }
189 | const state = detectState(line);
190 | const compilationStarted = state.compilationStarted;
191 | const compilationError = state.compilationError;
192 | const compilationComplete = state.compilationComplete;
193 |
194 | compilationErrorSinceStart =
195 | (!compilationStarted && compilationErrorSinceStart) || compilationError;
196 |
197 | if (state.fileEmitted !== null) {
198 | Signal.emitFile(state.fileEmitted);
199 | triggerOnEmit();
200 | }
201 |
202 | if (compilationStarted) {
203 | compilationId++;
204 | killProcesses(compilationId, false).then((previousCompilationId) => {
205 | if (previousCompilationId !== compilationId) {
206 | return;
207 | }
208 | runOnCompilationStarted();
209 | Signal.emitStarted();
210 | });
211 | }
212 |
213 | if (compilationComplete) {
214 | compilationId++;
215 | killProcesses(compilationId, false).then((previousCompilationId) => {
216 | if (previousCompilationId !== compilationId) {
217 | return;
218 | }
219 | runOnCompilationComplete();
220 |
221 | if (compilationErrorSinceStart) {
222 | Signal.emitFail();
223 | runOnFailureCommand();
224 | } else {
225 | if (firstTime) {
226 | firstTime = false;
227 | Signal.emitFirstSuccess();
228 | runOnFirstSuccessCommand();
229 | triggerOnEmit();
230 | }
231 |
232 | Signal.emitSuccess();
233 | runOnSuccessCommand();
234 | }
235 | });
236 | }
237 | });
238 |
239 | if (typeof process.on === 'function') {
240 | process.on('message', (msg: string) => {
241 | switch (msg) {
242 | case 'run-on-compilation-started-command':
243 | if (compilationStartedKiller) {
244 | compilationStartedKiller().then(runOnCompilationStarted);
245 | }
246 | break;
247 |
248 | case 'run-on-compilation-complete-command':
249 | if (compilationCompleteKiller) {
250 | compilationCompleteKiller().then(runOnCompilationComplete);
251 | }
252 | break;
253 |
254 | case 'run-on-first-success-command':
255 | if (firstSuccessKiller) {
256 | firstSuccessKiller().then(runOnFirstSuccessCommand);
257 | }
258 | break;
259 |
260 | case 'run-on-failure-command':
261 | if (failureKiller) {
262 | failureKiller().then(runOnFailureCommand);
263 | }
264 | break;
265 |
266 | case 'run-on-success-command':
267 | if (successKiller) {
268 | successKiller().then(runOnSuccessCommand);
269 | }
270 | break;
271 |
272 | case 'run-on-emit-command':
273 | if (emitKiller) {
274 | emitKiller().then(runOnEmitCommand);
275 | }
276 | break;
277 |
278 | default:
279 | console.log('Unknown message', msg);
280 | }
281 | });
282 | }
283 |
284 | const sendSignal = (msg: string) => {
285 | if (process.send) {
286 | process.send(msg);
287 | }
288 | };
289 |
290 | const Signal = {
291 | emitStarted: () => sendSignal('started'),
292 | emitFirstSuccess: () => sendSignal('first_success'),
293 | emitSuccess: () => sendSignal('success'),
294 | emitFail: () => sendSignal('compile_errors'),
295 | emitFile: (path: string) => sendSignal(`file_emitted:${path}`),
296 | };
297 |
298 | nodeCleanup((_exitCode: number | null, signal: string | null) => {
299 | if (signal) {
300 | tscProcess.kill(signal);
301 | }
302 | killProcesses(0, true).then(() => process.exit());
303 | // don't call cleanup handler again
304 | uninstall();
305 | return false;
306 | });
307 |
--------------------------------------------------------------------------------
/src/test/args-manager.test.ts:
--------------------------------------------------------------------------------
1 | import { extractArgs } from '../lib/args-manager';
2 |
3 | describe('Args Manager', () => {
4 | it('Should remove the runner args', () => {
5 | const { args } = extractArgs(['node', 'tsc-watch.js', '-d', '1.ts']);
6 | expect(args).toEqual(['-d', '1.ts', '--watch']);
7 | });
8 |
9 | it('Should remove custom args', () => {
10 | const { args } = extractArgs([
11 | 'node',
12 | 'tsc-watch.js',
13 | '--compiler',
14 | 'typescript/bin/tsc',
15 | '--nocolors',
16 | '--noclear',
17 | '--onsuccess',
18 | 'MY_SUCCESS',
19 | '--onfailure',
20 | 'MY_FAILURE',
21 | '--onfirstsuccess',
22 | 'MY_FIRST',
23 | '-d',
24 | '1.ts',
25 | ]);
26 | expect(args).toEqual(['-d', '1.ts', '--watch']);
27 | });
28 |
29 | it('Should force watch', () => {
30 | const { args } = extractArgs(['node', 'tsc-watch.js', '1.ts']);
31 | expect(args.indexOf('--watch')).toBeGreaterThan(-1);
32 | });
33 |
34 | it('Should not change the argv order options if watch was not specified (fixes --build option)', () => {
35 | const { args } = extractArgs([
36 | 'node',
37 | 'tsc-watch.js',
38 | '--build',
39 | '1.tsconfig.conf',
40 | '2.tsconfig.conf',
41 | ]);
42 | expect(args.indexOf('--build')).toBe(0);
43 | expect(args.indexOf('--watch')).toBe(3);
44 | });
45 |
46 | it('Should not insert args before --build (TS6369)', () => {
47 | const { args } = extractArgs([
48 | 'node',
49 | 'tsc-watch.js',
50 | '--signalEmittedFiles',
51 | '--build',
52 | '1.tsconfig.conf',
53 | '2.tsconfig.conf',
54 | ]);
55 | expect(args.indexOf('--build')).toBe(0);
56 | expect(args.indexOf('--listEmittedFiles')).toBe(1);
57 | })
58 |
59 | it('Should not re-add watch', () => {
60 | const isWatchParam = (elt: string) => elt === '-w' || elt === '--watch';
61 | expect(
62 | extractArgs(['node', 'tsc-watch.js', '-w', '1.ts']).args.filter(isWatchParam).length
63 | ).toBe(1);
64 | expect(
65 | extractArgs(['node', 'tsc-watch.js', '--watch', '1.ts']).args.filter(isWatchParam).length
66 | ).toBe(1);
67 | expect(
68 | extractArgs(['node', 'tsc-watch.js', '1.ts']).args.filter(isWatchParam).length
69 | ).toBe(1);
70 | });
71 |
72 | it('Should return the onFirstSuccessCommand', () => {
73 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onFirstSuccessCommand).toBe(null);
74 | expect(
75 | extractArgs(['node', 'tsc-watch.js', '--onFirstSuccess', 'COMMAND_TO_RUN', '1.ts'])
76 | .onFirstSuccessCommand,
77 | ).toBe('COMMAND_TO_RUN');
78 | });
79 |
80 | it('Should return the onSuccessCommand', () => {
81 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onSuccessCommand).toBe(null);
82 | expect(
83 | extractArgs(['node', 'tsc-watch.js', '--onSuccess', 'COMMAND_TO_RUN', '1.ts'])
84 | .onSuccessCommand,
85 | ).toBe('COMMAND_TO_RUN');
86 | });
87 |
88 | it('Should return the onFailureCommand', () => {
89 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onFailureCommand).toBe(null);
90 | expect(
91 | extractArgs(['node', 'tsc-watch.js', '--onFailure', 'COMMAND_TO_RUN', '1.ts'])
92 | .onFailureCommand,
93 | ).toBe('COMMAND_TO_RUN');
94 | });
95 |
96 | it('Should return the onCompilationStarted', () => {
97 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onCompilationStarted).toBe(null);
98 | expect(
99 | extractArgs(['node', 'tsc-watch.js', '--onCompilationStarted', 'COMMAND_TO_RUN', '1.ts'])
100 | .onCompilationStarted,
101 | ).toBe('COMMAND_TO_RUN');
102 | });
103 |
104 | it('Should return the onEmit', () => {
105 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onEmitCommand).toBe(null);
106 | expect(
107 | extractArgs(['node', 'tsc-watch.js', '--onEmit', 'COMMAND_TO_RUN', '1.ts'])
108 | .onEmitCommand,
109 | ).toBe('COMMAND_TO_RUN');
110 | });
111 |
112 | it('Should return the onEmitDebounceMs', () => {
113 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onEmitDebounceMs).toBe(300);
114 | expect(
115 | extractArgs(['node', 'tsc-watch.js', '--onEmitDebounceMs', '200', '1.ts'])
116 | .onEmitDebounceMs,
117 | ).toBe(200);
118 | });
119 |
120 | it('Should return the onCompilationComplete', () => {
121 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).onCompilationComplete).toBe(null);
122 | expect(
123 | extractArgs(['node', 'tsc-watch.js', '--onCompilationComplete', 'COMMAND_TO_RUN', '1.ts'])
124 | .onCompilationComplete,
125 | ).toBe('COMMAND_TO_RUN');
126 | });
127 |
128 | it('Should return the maxNodeMem', () => {
129 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).maxNodeMem).toBe(null);
130 | expect(extractArgs(['node', 'tsc-watch.js', '--maxNodeMem', '1024']).maxNodeMem).toBe('1024');
131 | });
132 |
133 | it('Should return the noColors', () => {
134 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).noColors).toBe(false);
135 | expect(extractArgs(['node', 'tsc-watch.js', '--noColors', '1.ts']).noColors).toBe(true);
136 | });
137 |
138 | it('Should return the noClear', () => {
139 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).noClear).toBe(false);
140 | expect(extractArgs(['node', 'tsc-watch.js', '--noClear', '1.ts']).noClear).toBe(true);
141 | });
142 |
143 | it('Should return the silent', () => {
144 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).silent).toBe(false);
145 | expect(extractArgs(['node', 'tsc-watch.js', '--silent', '1.ts']).silent).toBe(true);
146 | });
147 |
148 | it('Should return the signalEmittedFiles', () => {
149 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).signalEmittedFiles).toBe(false);
150 | expect(extractArgs(['node', 'tsc-watch.js', '--signalEmittedFiles', '1.ts']).signalEmittedFiles).toBe(true);
151 | expect(extractArgs(['node', 'tsc-watch.js', '--signalEmittedFiles', '1.ts']).args.includes('--listEmittedFiles')).toBe(true);
152 | });
153 |
154 | it('Should return requestedToListEmittedFiles when tsc native listEmittedFiles is set', () => {
155 | expect(extractArgs(['node', 'tsc-watch.js', '1.ts']).requestedToListEmittedFiles).toBe(false);
156 | expect(extractArgs(['node', 'tsc-watch.js', '--listEmittedFiles', '1.ts']).requestedToListEmittedFiles).toBe(true);
157 | expect(extractArgs(['node', 'tsc-watch.js', '--listEmittedFiles', '1.ts']).args.includes('--listEmittedFiles')).toBe(true);
158 | });
159 |
160 | });
161 |
--------------------------------------------------------------------------------
/src/test/client.test.ts:
--------------------------------------------------------------------------------
1 | import { arch, platform } from 'os';
2 | import { join } from 'path';
3 | import { TscWatchClient } from '../client';
4 | import {
5 | copyFixtures,
6 | FAILING_FILE,
7 | OUTPUT_FILE,
8 | PASSING_FILE,
9 | removeFixtures,
10 | waitFor,
11 | } from './test-utils';
12 | import type { ChildProcess } from 'child_process';
13 |
14 | const child_process = require('child_process');
15 |
16 | const compareFileLocation = (a: string, b: string) => {
17 | const aParts = a.split(/[\\\/]/);
18 | const bParts = b.split(/[\\\/]/);
19 | const aLast = aParts[aParts.length - 1];
20 | const bLast = bParts[bParts.length - 1];
21 | return aLast.toLowerCase() === bLast.toLowerCase();
22 | };
23 |
24 | describe('Client Events', () => {
25 | let watchClient: TscWatchClient;
26 | let callback: jest.Mock;
27 |
28 | beforeEach(() => {
29 | const tscWatchPath = require.resolve(join('..', '..', 'dist', 'lib', 'tsc-watch'));
30 | watchClient = new TscWatchClient(tscWatchPath);
31 | callback = jest.fn();
32 | copyFixtures();
33 | });
34 |
35 | afterEach(() => {
36 | watchClient.kill();
37 | removeFixtures();
38 | });
39 |
40 | describe('Events', () => {
41 | it('Should emit "started" on compilation start', () => {
42 | watchClient.on('started', callback);
43 | watchClient.start('--noClear', '--outFile', OUTPUT_FILE, FAILING_FILE);
44 | return waitFor(() => callback.mock.calls.length > 0);
45 | });
46 |
47 | it('Should emit "first_success" on first success', async () => {
48 | watchClient.on('first_success', callback);
49 | watchClient.start('--noClear', '--outFile', OUTPUT_FILE, PASSING_FILE);
50 | return waitFor(() => callback.mock.calls.length > 0);
51 | });
52 |
53 | it('Should emit "success" on first success', async () => {
54 | watchClient.on('success', callback);
55 | watchClient.start('--noClear', '--outFile', OUTPUT_FILE, PASSING_FILE);
56 | return waitFor(() => callback.mock.calls.length > 0);
57 | });
58 |
59 | it('Should deserialize and emit a "file_emitted" with the emitted file path', async function () {
60 | watchClient.on('file_emitted', callback);
61 | watchClient.start('--noClear', '--listEmittedFiles', '--outFile', OUTPUT_FILE, PASSING_FILE);
62 | return waitFor(() => {
63 | if (callback.mock.calls.length > 0) {
64 | const firstCall = callback.mock.calls[0];
65 | const callFirstArg = firstCall[0];
66 | return compareFileLocation(callFirstArg, OUTPUT_FILE);
67 | }
68 | });
69 | });
70 |
71 | it('Should fire "compile_errors" on when tsc compile errors occur', async () => {
72 | watchClient.on('compile_errors', callback);
73 | watchClient.start('--noClear', '--outFile', OUTPUT_FILE, FAILING_FILE);
74 | return waitFor(() => callback.mock.calls.length > 0);
75 | });
76 |
77 | it('Should fire back "exit" event when the process is exited by a signal', async function () {
78 | const forkSpy = jest.spyOn(child_process, 'fork');
79 | watchClient.on('exit', callback);
80 | watchClient.start('--noClear', '--outFile', OUTPUT_FILE, PASSING_FILE);
81 | const tscProcess: ChildProcess = forkSpy.mock.results[0].value;
82 |
83 | // Wait tsc-watch to be started and bound before to kill process
84 | await new Promise((resolve) => setTimeout(resolve, 1000));
85 | process.kill(tscProcess.pid, 9);
86 |
87 | await new Promise((resolve) => setTimeout(resolve, 1000));
88 | await waitFor(() => callback.mock.calls.length > 0);
89 | const expectedResult =
90 | platform() === 'darwin' && arch() === 'arm64' ? [null, 'SIGKILL'] : [1, null];
91 | expect(callback.mock.calls[0]).toEqual(expectedResult);
92 | });
93 | });
94 | });
95 |
--------------------------------------------------------------------------------
/src/test/compiler-provider.test.ts:
--------------------------------------------------------------------------------
1 | import { getCompilerPath } from '../lib/compiler-provider';
2 |
3 | describe('Compiler Provider', () => {
4 |
5 | it('Should return the custom compiler', () => {
6 | const resolver: any = jest.fn().mockImplementation((id: string) => 'TYPESCRIPT_COMPILER_PATH');
7 |
8 | const compilerPath = getCompilerPath('path/to/custom-compiler', resolver);
9 | expect(compilerPath).toBe('TYPESCRIPT_COMPILER_PATH');
10 | expect(resolver).toHaveBeenCalledWith('path/to/custom-compiler', { paths: [process.cwd()] });
11 | });
12 |
13 | it('Should return the local compiler', () => {
14 | const resolver: any = jest.fn().mockImplementation((id: string) => 'LOCAL_TYPESCRIPT_COMPILER_PATH');
15 |
16 |
17 | const compilerPath = getCompilerPath(null, resolver);
18 | expect(compilerPath).toBe('LOCAL_TYPESCRIPT_COMPILER_PATH');
19 | expect(resolver).toHaveBeenCalledWith('typescript/bin/tsc', { paths: [process.cwd()] });
20 | });
21 |
22 | it('Should return the global compiler, if local compiler is not found', () => {
23 | let callIndex = 0;
24 | const resolver: any = jest.fn().mockImplementation((id: string) => {
25 | if (callIndex === 0) {
26 | callIndex++;
27 | throw new Error('MODULE_NOT_FOUND');
28 | }
29 |
30 | return 'GLOBAL_TYPESCRIPT_COMPILER_PATH';
31 | });
32 |
33 |
34 | const compilerPath = getCompilerPath(null, resolver);
35 | expect(compilerPath).toBe('GLOBAL_TYPESCRIPT_COMPILER_PATH');
36 | });
37 | });
38 |
--------------------------------------------------------------------------------
/src/test/driver.ts:
--------------------------------------------------------------------------------
1 | import { ChildProcess, fork } from 'child_process';
2 | import { appendFileSync } from 'fs';
3 | import { join } from 'path';
4 | import { copyFixtures, FAILING_FILE, OUTPUT_FILE, PASSING_FILE, removeFixtures } from './test-utils';
5 |
6 | const noop = () => {};
7 |
8 | export class Driver {
9 | private subscriptions: Map;
10 | private process: ChildProcess | null;
11 |
12 | constructor() {
13 | copyFixtures();
14 | this.subscriptions = new Map();
15 | this.process = null;
16 | }
17 |
18 | public subscribe(processEventName: string, listener: Function) {
19 | this.subscriptions.set(processEventName, listener);
20 | return this;
21 | }
22 |
23 | public startWatch({ failFirst, pretty }: { failFirst?: boolean; pretty?: boolean } = {}): this {
24 | const params = [
25 | '--noClear',
26 | '--outFile',
27 | OUTPUT_FILE,
28 | failFirst ? FAILING_FILE : PASSING_FILE,
29 | ];
30 | if (pretty) {
31 | params.push('--pretty');
32 | }
33 | this.process = fork(join(process.cwd(), 'dist', 'lib', 'tsc-watch.js'), params, {
34 | stdio: 'inherit',
35 | });
36 |
37 | this.subscriptions.forEach((handler, evName) =>
38 | this.process!.on('message', (event) => (evName === event ? handler(event) : noop())),
39 | );
40 |
41 | return this;
42 | }
43 |
44 | public modifyToAValidChange(): this {
45 | appendFileSync(PASSING_FILE, '\n ');
46 | return this;
47 | }
48 |
49 | public modifyToAnInvalidChange(): this {
50 | appendFileSync(FAILING_FILE, '{{{');
51 | return this;
52 | }
53 |
54 | public dispose(): this {
55 | if (this.process && this.process.kill) {
56 | this.process.kill();
57 | this.process = null;
58 | }
59 |
60 | this.subscriptions.clear();
61 | removeFixtures();
62 | return this;
63 | }
64 |
65 | private wait(ms: number): Promise {
66 | return new Promise((resolve) => setTimeout(resolve, ms));
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/test/fixtures/failing.ts:
--------------------------------------------------------------------------------
1 | class T {
2 |
--------------------------------------------------------------------------------
/src/test/fixtures/passing.ts:
--------------------------------------------------------------------------------
1 | class T {}
2 |
--------------------------------------------------------------------------------
/src/test/stdout-manipulator.test.ts:
--------------------------------------------------------------------------------
1 | import { detectState, print } from '../lib/stdout-manipulator';
2 |
3 | describe('stdout-manipulator', () => {
4 | describe('detectState', () => {
5 | it('Should not detect anything for an empty line', async () => {
6 | const state = detectState('');
7 | expect(state).toEqual({
8 | compilationStarted: false,
9 | compilationError: false,
10 | compilationComplete: false,
11 | fileEmitted: null,
12 | });
13 | });
14 |
15 | it('Should not detect anything for an unknown line', async () => {
16 | const state = detectState('tsc: unknown statement');
17 | expect(state).toEqual({
18 | compilationStarted: false,
19 | compilationError: false,
20 | compilationComplete: false,
21 | fileEmitted: null,
22 | });
23 | });
24 |
25 | describe('Tested with typescript 4.5.5', () => {
26 | it('Should detect a compilation start', async () => {
27 | const { compilationStarted } = detectState('[\x1B[90m11:32:11 AM\x1B[0m] Starting compilation in watch mode...');
28 | expect(compilationStarted).toEqual(true);
29 | });
30 |
31 | it('Should detect a compilation error', async () => {
32 | const { compilationError } = detectState('\x1B[96mmodules/file.ts\x1B[0m:\x1B[93m17\x1B[0m:\x1B[93m49\x1B[0m - \x1B[91merror\x1B[0m\x1B[90m TS2345: \x1B[0mArgument of type \'string\' is not assignable to parameter of type \'never\'.');
33 | expect(compilationError).toEqual(true);
34 | });
35 |
36 | it('Should detect a compilation complete', async () => {
37 | const { compilationComplete } = detectState('[\x1B[90m11:32:26 AM\x1B[0m] Found 4 errors. Watching for file changes.');
38 | expect(compilationComplete).toEqual(true);
39 | });
40 |
41 | it('Should detect an emitted file', async () => {
42 | const { fileEmitted } = detectState('TSFILE: /my/dist/hello.js');
43 | expect(fileEmitted).toEqual('/my/dist/hello.js');
44 | });
45 | })
46 | });
47 |
48 | describe('print', () => {
49 | let forkSpy: any;
50 | beforeEach(() => {
51 | forkSpy = jest.spyOn(global.console, 'log').mockImplementation();
52 | });
53 |
54 | afterEach(() => {
55 | jest.clearAllMocks();
56 | });
57 |
58 | it('Should log raw line with default params', async () => {
59 | print('raw tsc line');
60 | expect(forkSpy.mock.calls).toEqual([['raw tsc line']])
61 | });
62 |
63 | it('Should not hide a normal line when signalEmittedFiles is true', async () => {
64 | print('any other line', { signalEmittedFiles: true });
65 | expect(forkSpy.mock.calls).toEqual([['any other line']])
66 | });
67 |
68 | describe("TSFILE support", () => {
69 | it('Should hide a TSFILE line when signalEmittedFiles is true', async () => {
70 | print('TSFILE: /home/emitted/file.js', { signalEmittedFiles: true });
71 | expect(forkSpy.mock.calls).toEqual([])
72 | });
73 |
74 | it('Should not hide a TSFILE line when signalEmittedFiles is true and native --listEmittedFiles (requestedToListEmittedFiles) is true', async () => {
75 | print('TSFILE: /home/emitted/file.js', { noColors: true, signalEmittedFiles: true, requestedToListEmittedFiles: true });
76 | expect(forkSpy.mock.calls).toEqual([['TSFILE: /home/emitted/file.js']])
77 | });
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/src/test/test-utils.ts:
--------------------------------------------------------------------------------
1 | import fsExtra from 'fs-extra';
2 | import { join } from 'path';
3 |
4 | export const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
5 |
6 | export const waitFor = (action: Function, timeout = 30000) => {
7 | const startTime = Date.now();
8 | return new Promise((resolve, reject) => {
9 | const retry = () => {
10 | if (Date.now() - timeout > startTime) {
11 | reject();
12 | } else {
13 | if (action()) {
14 | resolve();
15 | } else {
16 | wait(50).then(retry);
17 | }
18 | }
19 | };
20 |
21 | retry();
22 | });
23 | };
24 |
25 | export const FIXTURES_DIR = join(process.cwd(), 'src', 'test', 'fixtures');
26 | export const TMP_DIR = join(process.cwd(), 'tmp');
27 | export const TMP_FIXTURES_DIR = join(TMP_DIR, 'fixtures');
28 | export const OUTPUT_FILE = join(TMP_FIXTURES_DIR, 'output.js');
29 | export const FAILING_FILE = join(TMP_FIXTURES_DIR, 'failing.ts');
30 | export const PASSING_FILE = join(TMP_FIXTURES_DIR, 'passing.ts');
31 |
32 | export function copyFixtures() {
33 | fsExtra.copySync(FIXTURES_DIR, TMP_FIXTURES_DIR);
34 | }
35 |
36 | export function removeFixtures() {
37 | fsExtra.removeSync(TMP_DIR);
38 | }
39 |
--------------------------------------------------------------------------------
/src/test/tsc-watch.test.ts:
--------------------------------------------------------------------------------
1 | import { Driver } from './driver';
2 | import { waitFor, wait } from './test-utils';
3 |
4 | describe('TSC-Watch child process messages', () => {
5 | let driver: Driver;
6 |
7 | beforeEach(() => (driver = new Driver()));
8 | afterEach(() => driver.dispose());
9 |
10 | it('Should send "started" on compilation start', async () => {
11 | const listener = jest.fn();
12 | driver.subscribe('started', listener).startWatch();
13 | await wait(5000);
14 | driver.modifyToAValidChange();
15 | return waitFor(() => listener.mock.calls.length === 2);
16 | });
17 |
18 | it('Should send "first_success" on first success', async () => {
19 | const listener = jest.fn();
20 | driver.subscribe('first_success', listener).startWatch();
21 | await wait(5000);
22 | driver.modifyToAValidChange();
23 | return waitFor(() => listener.mock.calls.length === 1);
24 | });
25 |
26 | it('Should send "success" on subsequent successes', async () => {
27 | const listener = jest.fn();
28 | driver.subscribe('success', listener).startWatch();
29 | await wait(5000);
30 | driver.modifyToAValidChange();
31 | return waitFor(() => listener.mock.calls.length === 2);
32 | });
33 |
34 | it('Should send "compile_errors" when tsc compile errors occur', async () => {
35 | const listener = jest.fn();
36 | driver.subscribe('compile_errors', listener).startWatch({ failFirst: true });
37 | await wait(5000);
38 | driver.modifyToAnInvalidChange();
39 |
40 | return waitFor(() => listener.mock.calls.length === 2);
41 | });
42 |
43 | it('Should send "compile_errors" when pretty param was set', async () => {
44 | const listener = jest.fn();
45 | driver.subscribe('compile_errors', listener).startWatch({ failFirst: true, pretty: true });
46 | return waitFor(() => listener.mock.calls.length === 1);
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/tsc-watch-client-example.js:
--------------------------------------------------------------------------------
1 | const readline = require('readline');
2 | const { TscWatchClient } = require('./client');
3 |
4 | const client = new TscWatchClient();
5 |
6 | client.on('started', () => {
7 | console.log('Compilation started');
8 | });
9 |
10 | client.on('first_success', () => {
11 | console.log('Interactive mode');
12 | console.log(' Press "r" to re-run the onSuccess command, esc to exit.\n');
13 | });
14 |
15 | client.on('success', () => {
16 | console.log('Yey success!');
17 | });
18 |
19 | client.on('compile_errors', () => {
20 | console.log('Ho no!');
21 | });
22 |
23 | client.start(
24 | '--onSuccess',
25 | 'node ./example/main.js',
26 | '--noClear',
27 | '--project',
28 | './example/tsconfig.json',
29 | );
30 |
31 | readline.emitKeypressEvents(process.stdin);
32 | process.stdin.on('keypress', (str, key) => {
33 | if (key.name == 'escape' || (key && key.ctrl && key.name == 'c')) {
34 | client.kill();
35 | process.stdin.pause();
36 | } else {
37 | if (str && str.toLowerCase() === 'r') {
38 | client.runOnSuccessCommand();
39 | }
40 | }
41 | });
42 |
43 | process.stdin.setRawMode(true);
44 | process.stdin.resume();
45 |
--------------------------------------------------------------------------------
/tsconfig-client.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2016",
4 | "module": "CommonJS",
5 | "moduleResolution": "node",
6 | "declaration": true,
7 | "outDir": "./dist/client",
8 | "rootDir": "./src/client",
9 | "isolatedModules": true,
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "skipLibCheck": true
15 | },
16 | "include": ["src/client/index.ts"]
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es2016",
4 | "module": "CommonJS",
5 | "moduleResolution": "node",
6 | "outDir": "./dist/lib",
7 | "rootDir": "./src/lib",
8 | "esModuleInterop": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "allowSyntheticDefaultImports": true,
11 | "strict": true,
12 | "skipLibCheck": true
13 | },
14 | "typeRoots": ["./node_modules/@types", "./@types"],
15 | "include": ["src/lib/**/*.ts"]
16 | }
17 |
--------------------------------------------------------------------------------