node/test/sequential/test-watch-mode.mjs
Dario Piotrowicz 4acb854039
watch: fix watch args not being properly filtered
currently when --watch is used, the argv arguments that
the target script receives are filtered so that they don't
include watch related arguments, however the current
filtering logic is incorrect and it causes some watch values
to incorrectly pass the filtering, the changes here address
such issue

PR-URL: https://github.com/nodejs/node/pull/57936
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
2025-05-05 15:08:04 +00:00

844 lines
28 KiB
JavaScript

import * as common from '../common/index.mjs';
import tmpdir from '../common/tmpdir.js';
import assert from 'node:assert';
import path from 'node:path';
import { execPath } from 'node:process';
import { describe, it } from 'node:test';
import { spawn } from 'node:child_process';
import { writeFileSync, readFileSync, mkdirSync } from 'node:fs';
import { inspect } from 'node:util';
import { pathToFileURL } from 'node:url';
import { once } from 'node:events';
import { createInterface } from 'node:readline';
if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');
const supportsRecursive = common.isMacOS || common.isWindows;
function restart(file, content = readFileSync(file)) {
// To avoid flakiness, we save the file repeatedly until test is done
writeFileSync(file, content);
const timer = setInterval(() => writeFileSync(file, content), common.platformTimeout(2500));
return () => clearInterval(timer);
}
let tmpFiles = 0;
function createTmpFile(content = 'console.log("running");', ext = '.js', basename = tmpdir.path) {
const file = path.join(basename, `${tmpFiles++}${ext}`);
writeFileSync(file, content);
return file;
}
function runInBackground({ args = [], options = {}, completed = 'Completed running', shouldFail = false }) {
let future = Promise.withResolvers();
let child;
let stderr = '';
let stdout = [];
const run = () => {
args.unshift('--no-warnings');
child = spawn(execPath, args, { encoding: 'utf8', stdio: 'pipe', ...options });
child.stderr.on('data', (data) => {
stderr += data;
});
const rl = createInterface({ input: child.stdout });
rl.on('line', (data) => {
if (!data.startsWith('Waiting for graceful termination') && !data.startsWith('Gracefully restarted')) {
stdout.push(data);
if (data.startsWith(completed)) {
future.resolve({ stderr, stdout });
future = Promise.withResolvers();
stdout = [];
stderr = '';
} else if (data.startsWith('Failed running')) {
if (shouldFail) {
future.resolve({ stderr, stdout });
} else {
future.reject({ stderr, stdout });
}
future = Promise.withResolvers();
stdout = [];
stderr = '';
}
}
});
};
return {
async done() {
child?.kill();
future.resolve();
return { stdout, stderr };
},
restart(timeout = 1000) {
if (!child) {
run();
}
const timer = setTimeout(() => {
if (!future.resolved) {
child.kill();
future.reject(new Error('Timed out waiting for restart'));
}
}, timeout);
return future.promise.finally(() => {
clearTimeout(timer);
});
}
};
}
async function runWriteSucceed({
file,
watchedFile,
watchFlag = '--watch',
args = [file],
completed = 'Completed running',
restarts = 2,
options = {},
shouldFail = false
}) {
args.unshift('--no-warnings');
if (watchFlag !== null) args.unshift(watchFlag);
const child = spawn(execPath, args, { encoding: 'utf8', stdio: 'pipe', ...options });
let completes = 0;
let cancelRestarts = () => {};
let stderr = '';
const stdout = [];
child.stderr.on('data', (data) => {
stderr += data;
});
try {
// Break the chunks into lines
for await (const data of createInterface({ input: child.stdout })) {
if (!data.startsWith('Waiting for graceful termination') && !data.startsWith('Gracefully restarted')) {
stdout.push(data);
}
if (data.startsWith(completed)) {
completes++;
if (completes === restarts) {
break;
}
if (completes === 1) {
cancelRestarts = restart(watchedFile);
}
}
if (!shouldFail && data.startsWith('Failed running')) {
break;
}
}
} finally {
child.kill();
cancelRestarts();
}
return { stdout, stderr, pid: child.pid };
}
async function failWriteSucceed({ file, watchedFile }) {
const child = spawn(execPath, ['--watch', '--no-warnings', file], { encoding: 'utf8', stdio: 'pipe' });
let cancelRestarts = () => {};
try {
// Break the chunks into lines
for await (const data of createInterface({ input: child.stdout })) {
if (data.startsWith('Completed running')) {
break;
}
if (data.startsWith('Failed running')) {
cancelRestarts = restart(watchedFile, 'console.log("test has ran");');
}
}
} finally {
child.kill();
cancelRestarts();
}
}
tmpdir.refresh();
describe('watch mode', { concurrency: !process.env.TEST_PARALLEL, timeout: 60_000 }, () => {
it('should watch changes to a file', async () => {
const file = createTmpFile();
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, watchFlag: '--watch=true', options: {
timeout: 10000
} });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should watch changes to a file - event loop ended', async () => {
const file = createTmpFile();
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should reload env variables when --env-file changes', async () => {
const envKey = `TEST_ENV_${Date.now()}`;
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey});`);
const envFile = createTmpFile(`${envKey}=value1`, '.env');
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });
try {
await restart();
writeFileSync(envFile, `${envKey}=value2`);
// Second restart, after env change
const { stdout, stderr } = await restart();
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
`Restarting ${inspect(jsFile)}`,
'ENV: value2',
`Completed running ${inspect(jsFile)}. Waiting for file changes before restarting...`,
]);
} finally {
await done();
}
});
it('should load new env variables when --env-file changes', async () => {
const envKey = `TEST_ENV_${Date.now()}`;
const envKey2 = `TEST_ENV_2_${Date.now()}`;
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey} + '\\n' + 'ENV2: ' + process.env.${envKey2});`);
const envFile = createTmpFile(`${envKey}=value1`, '.env');
const { done, restart } = runInBackground({ args: ['--watch', `--env-file=${envFile}`, jsFile] });
try {
await restart();
writeFileSync(envFile, `${envKey}=value1\n${envKey2}=newValue`);
// Second restart, after env change
const { stderr, stdout } = await restart();
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
`Restarting ${inspect(jsFile)}`,
'ENV: value1',
'ENV2: newValue',
`Completed running ${inspect(jsFile)}. Waiting for file changes before restarting...`,
]);
} finally {
await done();
}
});
it('should load new env variables when --env-file-if-exists changes', async () => {
const envKey = `TEST_ENV_${Date.now()}`;
const envKey2 = `TEST_ENV_2_${Date.now()}`;
const jsFile = createTmpFile(`console.log('ENV: ' + process.env.${envKey} + '\\n' + 'ENV2: ' + process.env.${envKey2});`);
const envFile = createTmpFile(`${envKey}=value1`, '.env');
const { done, restart } = runInBackground({ args: ['--watch', `--env-file-if-exists=${envFile}`, jsFile] });
try {
await restart();
writeFileSync(envFile, `${envKey}=value1\n${envKey2}=newValue`);
// Second restart, after env change
const { stderr, stdout } = await restart();
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
`Restarting ${inspect(jsFile)}`,
'ENV: value1',
'ENV2: newValue',
`Completed running ${inspect(jsFile)}. Waiting for file changes before restarting...`,
]);
} finally {
await done();
}
});
it('should watch changes to a failing file', async () => {
const file = createTmpFile('throw new Error("fails");');
const { stderr, stdout } = await runWriteSucceed({
file,
watchedFile: file,
completed: 'Failed running',
shouldFail: true
});
assert.match(stderr, /Error: fails\r?\n/);
assert.deepStrictEqual(stdout, [
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should watch changes to a file with watch-path', {
skip: !supportsRecursive,
}, async () => {
const dir = tmpdir.resolve('subdir1');
mkdirSync(dir);
const file = createTmpFile();
const watchedFile = createTmpFile('', '.js', dir);
const args = ['--watch-path', dir, file];
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile, args });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
assert.strictEqual(stderr, '');
});
it('should watch when running an non-existing file - when specified under --watch-path', {
skip: !supportsRecursive
}, async () => {
const dir = tmpdir.resolve('subdir2');
mkdirSync(dir);
const file = path.join(dir, 'non-existing.js');
const watchedFile = createTmpFile('', '.js', dir);
const args = ['--watch-path', dir, file];
const { stderr, stdout } = await runWriteSucceed({
file,
watchedFile,
args,
completed: 'Failed running',
shouldFail: true
});
assert.match(stderr, /Error: Cannot find module/g);
assert.deepStrictEqual(stdout, [
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should watch when running an non-existing file - when specified under --watch-path with equals', {
skip: !supportsRecursive
}, async () => {
const dir = tmpdir.resolve('subdir3');
mkdirSync(dir);
const file = path.join(dir, 'non-existing.js');
const watchedFile = createTmpFile('', '.js', dir);
const args = [`--watch-path=${dir}`, file];
const { stderr, stdout } = await runWriteSucceed({
file,
watchedFile,
args,
completed: 'Failed running',
shouldFail: true
});
assert.match(stderr, /Error: Cannot find module/g);
assert.deepStrictEqual(stdout, [
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
`Failed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should watch changes to a file - event loop blocked', { timeout: 10_000 }, async () => {
const file = createTmpFile(`
console.log("running");
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0);
console.log("don't show me");`);
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, completed: 'running' });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Restarting ${inspect(file)}`,
'running',
]);
});
it('should watch changes to dependencies - cjs', async () => {
const dependency = createTmpFile('module.exports = {};');
const file = createTmpFile(`
const dependency = require(${JSON.stringify(dependency)});
console.log(dependency);
`);
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: dependency });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'{}',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'{}',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should watch changes to dependencies - esm', async () => {
const dependency = createTmpFile('module.exports = {};');
const file = createTmpFile(`
import dependency from ${JSON.stringify(pathToFileURL(dependency))};
console.log(dependency);
`, '.mjs');
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: dependency });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'{}',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'{}',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should restart multiple times', async () => {
const file = createTmpFile();
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, restarts: 3 });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should pass arguments to file', async () => {
const file = createTmpFile(`
const { parseArgs } = require('node:util');
const { values } = parseArgs({ options: { random: { type: 'string' } } });
console.log(values.random);
`);
const random = Date.now().toString();
const args = [file, '--random', random];
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, args });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
random,
`Completed running ${inspect(`${file} --random ${random}`)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(`${file} --random ${random}`)}`,
random,
`Completed running ${inspect(`${file} --random ${random}`)}. Waiting for file changes before restarting...`,
]);
});
it('should load --require modules in the watched process, and not in the orchestrator process', async () => {
const file = createTmpFile();
const required = createTmpFile('process._rawDebug(\'pid\', process.pid);');
const args = ['--require', required, file];
const { stdout, pid, stderr } = await runWriteSucceed({ file, watchedFile: file, args });
const importPid = parseInt(stderr[0].split(' ')[1], 10);
assert.notStrictEqual(pid, importPid);
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should load --import modules in the watched process, and not in the orchestrator process', async () => {
const file = createTmpFile();
const imported = "data:text/javascript,process._rawDebug('pid', process.pid);";
const args = ['--import', imported, file];
const { stdout, pid, stderr } = await runWriteSucceed({ file, watchedFile: file, args });
const importPid = parseInt(stderr.split('\n', 1)[0].split(' ', 2)[1], 10);
assert.notStrictEqual(importPid, NaN);
assert.notStrictEqual(pid, importPid);
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
// TODO: Remove skip after https://github.com/nodejs/node/pull/45271 lands
it('should not watch when running an missing file', {
skip: !supportsRecursive
}, async () => {
const nonExistingfile = tmpdir.resolve(`${tmpFiles++}.js`);
await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile });
});
it('should not watch when running an missing mjs file', {
skip: !supportsRecursive
}, async () => {
const nonExistingfile = tmpdir.resolve(`${tmpFiles++}.mjs`);
await failWriteSucceed({ file: nonExistingfile, watchedFile: nonExistingfile });
});
it('should watch changes to previously missing dependency', {
skip: !supportsRecursive
}, async () => {
const dependency = tmpdir.resolve(`${tmpFiles++}.js`);
const relativeDependencyPath = `./${path.basename(dependency)}`;
const dependant = createTmpFile(`console.log(require('${relativeDependencyPath}'))`);
await failWriteSucceed({ file: dependant, watchedFile: dependency });
});
it('should watch changes to previously missing ESM dependency', {
skip: !supportsRecursive
}, async () => {
const relativeDependencyPath = `./${tmpFiles++}.mjs`;
const dependency = tmpdir.resolve(relativeDependencyPath);
const dependant = createTmpFile(`import ${JSON.stringify(relativeDependencyPath)}`, '.mjs');
await failWriteSucceed({ file: dependant, watchedFile: dependency });
});
it('should clear output between runs', async () => {
const file = createTmpFile();
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should preserve output when --watch-preserve-output flag is passed', async () => {
const file = createTmpFile();
const args = ['--watch-preserve-output', file];
const { stderr, stdout } = await runWriteSucceed({ file, watchedFile: file, args });
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch-path=./foo --require ./bar.js`', {
skip: !supportsRecursive,
}, async () => {
const projectDir = tmpdir.resolve('project2');
mkdirSync(projectDir);
const dir = path.join(projectDir, 'watched-dir');
mkdirSync(dir);
writeFileSync(path.join(projectDir, 'some.js'), 'console.log(\'hello\')');
const file = createTmpFile('console.log(\'running\');', '.js', projectDir);
const watchedFile = createTmpFile('', '.js', dir);
const args = [`--watch-path=${dir}`, '--require', './some.js', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile, args, options: {
cwd: projectDir
}
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch-path=./foo --require=./bar.js`', {
skip: !supportsRecursive,
}, async () => {
const projectDir = tmpdir.resolve('project3');
mkdirSync(projectDir);
const dir = path.join(projectDir, 'watched-dir');
mkdirSync(dir);
writeFileSync(path.join(projectDir, 'some.js'), "console.log('hello')");
const file = createTmpFile("console.log('running');", '.js', projectDir);
const watchedFile = createTmpFile('', '.js', dir);
const args = [`--watch-path=${dir}`, '--require=./some.js', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile, args, options: {
cwd: projectDir
}
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch-path ./foo --require ./bar.js`', {
skip: !supportsRecursive,
}, async () => {
const projectDir = tmpdir.resolve('project5');
mkdirSync(projectDir);
const dir = path.join(projectDir, 'watched-dir');
mkdirSync(dir);
writeFileSync(path.join(projectDir, 'some.js'), 'console.log(\'hello\')');
const file = createTmpFile('console.log(\'running\');', '.js', projectDir);
const watchedFile = createTmpFile('', '.js', dir);
const args = ['--watch-path', `${dir}`, '--require', './some.js', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile, args, options: {
cwd: projectDir
}
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch-path=./foo --require=./bar.js`', {
skip: !supportsRecursive,
}, async () => {
const projectDir = tmpdir.resolve('project6');
mkdirSync(projectDir);
const dir = path.join(projectDir, 'watched-dir');
mkdirSync(dir);
writeFileSync(path.join(projectDir, 'some.js'), "console.log('hello')");
const file = createTmpFile("console.log('running');", '.js', projectDir);
const watchedFile = createTmpFile('', '.js', dir);
const args = ['--watch-path', `${dir}`, '--require=./some.js', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile, args, options: {
cwd: projectDir
}
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch --inspect`', async () => {
const file = createTmpFile();
const args = ['--watch', '--inspect', file];
const { stdout, stderr } = await runWriteSucceed({ file, watchedFile: file, watchFlag: null, args });
assert.match(stderr, /listening on ws:\/\//);
assert.deepStrictEqual(stdout, [
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should run when `--watch -r ./foo.js`', async () => {
const projectDir = tmpdir.resolve('project7');
mkdirSync(projectDir);
const dir = path.join(projectDir, 'watched-dir');
mkdirSync(dir);
writeFileSync(path.join(projectDir, 'some.js'), "console.log('hello')");
const file = createTmpFile("console.log('running');", '.js', projectDir);
const args = ['--watch', '-r', './some.js', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile: file, watchFlag: null, args, options: { cwd: projectDir }
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
`Restarting ${inspect(file)}`,
'hello',
'running',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('should pass IPC messages from a spawning parent to the child and back', async () => {
const file = createTmpFile(`console.log('running');
process.on('message', (message) => {
if (message === 'exit') {
process.exit(0);
} else {
console.log('Received:', message);
process.send(message);
}
})`);
const child = spawn(
execPath,
[
'--watch',
'--no-warnings',
file,
],
{
encoding: 'utf8',
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
},
);
let stderr = '';
let stdout = '';
child.stdout.on('data', (data) => stdout += data);
child.stderr.on('data', (data) => stderr += data);
async function waitForEcho(msg) {
const receivedPromise = new Promise((resolve) => {
const fn = (message) => {
if (message === msg) {
child.off('message', fn);
resolve();
}
};
child.on('message', fn);
});
child.send(msg);
await receivedPromise;
}
async function waitForText(text) {
const seenPromise = new Promise((resolve) => {
const fn = (data) => {
if (data.toString().includes(text)) {
resolve();
child.stdout.off('data', fn);
}
};
child.stdout.on('data', fn);
});
await seenPromise;
}
await waitForText('running');
await waitForEcho('first message');
const stopRestarts = restart(file);
await waitForText('running');
stopRestarts();
await waitForEcho('second message');
const exitedPromise = once(child, 'exit');
child.send('exit');
await waitForText('Completed');
child.disconnect();
child.kill();
await exitedPromise;
assert.strictEqual(stderr, '');
const lines = stdout.split(/\r?\n/).filter(Boolean);
assert.deepStrictEqual(lines, [
'running',
'Received: first message',
`Restarting ${inspect(file)}`,
'running',
'Received: second message',
`Completed running ${inspect(file)}. Waiting for file changes before restarting...`,
]);
});
it('when multiple `--watch` flags are provided should run as if only one was', async () => {
const projectDir = tmpdir.resolve('project-multi-flag');
mkdirSync(projectDir);
const file = createTmpFile(`
console.log(
process.argv.some(arg => arg === '--watch')
? 'Error: unexpected --watch args present'
: 'no --watch args present'
);`, '.js', projectDir);
const args = ['--watch', '--watch', file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile: file, watchFlag: null, args, options: { cwd: projectDir }
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'no --watch args present',
`Completed running ${inspect(file)}`,
`Restarting ${inspect(file)}`,
'no --watch args present',
`Completed running ${inspect(file)}`,
]);
});
it('`--watch-path` ars without `=` used alongside `--watch` should not make it into the script', async () => {
const projectDir = tmpdir.resolve('project-watch-watch-path-args');
mkdirSync(projectDir);
const file = createTmpFile(`
console.log(
process.argv.slice(2).some(arg => arg.endsWith('.js'))
? 'some cli args end with .js'
: 'no cli arg ends with .js'
);`, '.js', projectDir);
const args = ['--watch', `--watch-path`, file, file];
const { stdout, stderr } = await runWriteSucceed({
file, watchedFile: file, watchFlag: null, args, options: { cwd: projectDir }
});
assert.strictEqual(stderr, '');
assert.deepStrictEqual(stdout, [
'no cli arg ends with .js',
`Completed running ${inspect(file)}`,
`Restarting ${inspect(file)}`,
'no cli arg ends with .js',
`Completed running ${inspect(file)}`,
]);
});
});