Skip to content

Commit

Permalink
fix(watch): wait for the process to exit (#298)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber committed Sep 1, 2023
1 parent 247bbc0 commit a4cb800
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 19 deletions.
61 changes: 42 additions & 19 deletions src/watch/index.ts
Expand Up @@ -15,6 +15,18 @@ import {
log,
} from './utils';

const killProcess = async (
childProcess: ChildProcess,
) => {
const waitForExit = new Promise((resolve) => {
childProcess.on('exit', resolve);
});

childProcess.kill();

await waitForExit;
};

const flags = {
noCache: {
type: Boolean,
Expand Down Expand Up @@ -63,26 +75,10 @@ export const watchCommand = command({

let runProcess: ChildProcess | undefined;

const reRun = debounce(() => {
if (
runProcess
&& (!runProcess.killed && runProcess.exitCode === null)
) {
runProcess.kill();
}

// Not first run
if (runProcess) {
log('rerunning');
}
const spawnProcess = () => {
const childProcess = run(rawArgvs, options);

if (options.clearScreen) {
process.stdout.write(clearScreen);
}

runProcess = run(rawArgvs, options);

runProcess.on('message', (data) => {
childProcess.on('message', (data) => {
// Collect run-time dependencies to watch
if (
data
Expand All @@ -104,6 +100,33 @@ export const watchCommand = command({
}
}
});

return childProcess;
};

let waitingExits = false;
const reRun = debounce(async () => {
if (waitingExits) {
log('forcing restart');
runProcess!.kill('SIGKILL');
return;
}

// If running process
if (runProcess?.exitCode === null) {
log('restarting');
waitingExits = true;
await killProcess(runProcess);
waitingExits = false;
} else {
log('rerunning');
}

if (options.clearScreen) {
process.stdout.write(clearScreen);
}

runProcess = spawnProcess();
}, 100);

reRun();
Expand Down
62 changes: 62 additions & 0 deletions tests/specs/watch.ts
Expand Up @@ -106,6 +106,68 @@ export default testSuite(async ({ describe }, fixturePath: string) => {
await tsxProcess;
}, 10_000);

test('wait for exit', async ({ onTestFinish }) => {
const fixture = await createFixture({
'index.js': `
console.log('start');
const sleepSync = (delay) => {
const waitTill = Date.now() + delay;
while (Date.now() < waitTill) {}
};
process.on('exit', () => {
sleepSync(300);
console.log('end');
});
`,
});

onTestFinish(async () => await fixture.rm());

const tsxProcess = tsx({
args: [
'watch',
path.join(fixture.path, 'index.js'),
],
});

const stdout = await new Promise<string>((resolve) => {
const buffers: Buffer[] = [];
const waitingOn: [string, (() => void)][] = [
['start\n', () => {
tsxProcess.stdin?.write('enter');
}],
['end\n', () => {}],
];

let currentWaitingOn = waitingOn.shift();
async function onStdOut(data: Buffer) {
buffers.push(data);
const chunkString = data.toString();

if (currentWaitingOn) {
const [expected, callback] = currentWaitingOn!;

// eslint-disable-next-line unicorn/prefer-regexp-test
if (chunkString.match(expected)) {
callback();
currentWaitingOn = waitingOn.shift();
if (!currentWaitingOn) {
tsxProcess.kill();
resolve(Buffer.concat(buffers).toString());
}
}
}
}

tsxProcess.stdout!.on('data', onStdOut);
tsxProcess.stderr!.on('data', onStdOut);
});

expect(stdout).toMatch(/start[\s\S]+end/);

await tsxProcess;
}, 10_000);

describe('help', ({ test }) => {
test('shows help', async () => {
const tsxProcess = await tsx({
Expand Down

0 comments on commit a4cb800

Please sign in to comment.