Skip to content

Commit a4cb800

Browse files
authoredSep 1, 2023
fix(watch): wait for the process to exit (#298)
1 parent 247bbc0 commit a4cb800

File tree

2 files changed

+104
-19
lines changed

2 files changed

+104
-19
lines changed
 

‎src/watch/index.ts

+42-19
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ import {
1515
log,
1616
} from './utils';
1717

18+
const killProcess = async (
19+
childProcess: ChildProcess,
20+
) => {
21+
const waitForExit = new Promise((resolve) => {
22+
childProcess.on('exit', resolve);
23+
});
24+
25+
childProcess.kill();
26+
27+
await waitForExit;
28+
};
29+
1830
const flags = {
1931
noCache: {
2032
type: Boolean,
@@ -63,26 +75,10 @@ export const watchCommand = command({
6375

6476
let runProcess: ChildProcess | undefined;
6577

66-
const reRun = debounce(() => {
67-
if (
68-
runProcess
69-
&& (!runProcess.killed && runProcess.exitCode === null)
70-
) {
71-
runProcess.kill();
72-
}
73-
74-
// Not first run
75-
if (runProcess) {
76-
log('rerunning');
77-
}
78+
const spawnProcess = () => {
79+
const childProcess = run(rawArgvs, options);
7880

79-
if (options.clearScreen) {
80-
process.stdout.write(clearScreen);
81-
}
82-
83-
runProcess = run(rawArgvs, options);
84-
85-
runProcess.on('message', (data) => {
81+
childProcess.on('message', (data) => {
8682
// Collect run-time dependencies to watch
8783
if (
8884
data
@@ -104,6 +100,33 @@ export const watchCommand = command({
104100
}
105101
}
106102
});
103+
104+
return childProcess;
105+
};
106+
107+
let waitingExits = false;
108+
const reRun = debounce(async () => {
109+
if (waitingExits) {
110+
log('forcing restart');
111+
runProcess!.kill('SIGKILL');
112+
return;
113+
}
114+
115+
// If running process
116+
if (runProcess?.exitCode === null) {
117+
log('restarting');
118+
waitingExits = true;
119+
await killProcess(runProcess);
120+
waitingExits = false;
121+
} else {
122+
log('rerunning');
123+
}
124+
125+
if (options.clearScreen) {
126+
process.stdout.write(clearScreen);
127+
}
128+
129+
runProcess = spawnProcess();
107130
}, 100);
108131

109132
reRun();

‎tests/specs/watch.ts

+62
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,68 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
106106
await tsxProcess;
107107
}, 10_000);
108108

109+
test('wait for exit', async ({ onTestFinish }) => {
110+
const fixture = await createFixture({
111+
'index.js': `
112+
console.log('start');
113+
const sleepSync = (delay) => {
114+
const waitTill = Date.now() + delay;
115+
while (Date.now() < waitTill) {}
116+
};
117+
process.on('exit', () => {
118+
sleepSync(300);
119+
console.log('end');
120+
});
121+
`,
122+
});
123+
124+
onTestFinish(async () => await fixture.rm());
125+
126+
const tsxProcess = tsx({
127+
args: [
128+
'watch',
129+
path.join(fixture.path, 'index.js'),
130+
],
131+
});
132+
133+
const stdout = await new Promise<string>((resolve) => {
134+
const buffers: Buffer[] = [];
135+
const waitingOn: [string, (() => void)][] = [
136+
['start\n', () => {
137+
tsxProcess.stdin?.write('enter');
138+
}],
139+
['end\n', () => {}],
140+
];
141+
142+
let currentWaitingOn = waitingOn.shift();
143+
async function onStdOut(data: Buffer) {
144+
buffers.push(data);
145+
const chunkString = data.toString();
146+
147+
if (currentWaitingOn) {
148+
const [expected, callback] = currentWaitingOn!;
149+
150+
// eslint-disable-next-line unicorn/prefer-regexp-test
151+
if (chunkString.match(expected)) {
152+
callback();
153+
currentWaitingOn = waitingOn.shift();
154+
if (!currentWaitingOn) {
155+
tsxProcess.kill();
156+
resolve(Buffer.concat(buffers).toString());
157+
}
158+
}
159+
}
160+
}
161+
162+
tsxProcess.stdout!.on('data', onStdOut);
163+
tsxProcess.stderr!.on('data', onStdOut);
164+
});
165+
166+
expect(stdout).toMatch(/start[\s\S]+end/);
167+
168+
await tsxProcess;
169+
}, 10_000);
170+
109171
describe('help', ({ test }) => {
110172
test('shows help', async () => {
111173
const tsxProcess = await tsx({

0 commit comments

Comments
 (0)
Please sign in to comment.