text: import childProcess from 'child_process';
  import {fileURLToPath} from 'url';
  import {Worker} from 'worker_threads';
  
  import Emittery from 'emittery';
  import pEvent from 'p-event';
  
  import {controlFlow} from './ipc-flow-control.cjs';
  
  let workerPath = new URL('worker/base.js', import.meta.url);
  export function _testOnlyReplaceWorkerPath(replacement) {
  	workerPath = replacement;
  }
  
  let forkCounter = 0;
  
  const createWorker = (options, execArgv) => {
  	let worker;
  	let postMessage;
  	let close;
  	if (options.workerThreads) {
  		worker = new Worker(workerPath, {
  			argv: options.workerArgv,
  			env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
  			execArgv,
  			workerData: {
  				options
  			},
  			trackUnmanagedFds: true,
  			stdin: true,
  			stdout: true,
  			stderr: true
  		});
  		postMessage = worker.postMessage.bind(worker);
  
  		// Ensure we've seen this event before we terminate the worker thread, as a
  		// workaround for https://github.com/nodejs/node/issues/38418.
  		const starting = pEvent(worker, 'message', ({ava}) => ava && ava.type === 'starting');
  
  		close = async () => {
  			try {
  				await starting;
  				await worker.terminate();
  			} finally {
  				// No-op
  			}
  		};
  	} else {
  		worker = childProcess.fork(fileURLToPath(workerPath), options.workerArgv, {
  			cwd: options.projectDir,
  			silent: true,
  			env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
  			execArgv
  		});
  		postMessage = controlFlow(worker);
  		close = async () => worker.kill();
  	}
  
  	return {
  		worker,
  		postMessage,
  		close
  	};
  };
  
  export default (file, options, execArgv = process.execArgv) => {
  	// TODO: this can be changed to use `threadId` when using worker_threads
  	const forkId = `fork/${++forkCounter}`;
  
  	let finished = false;
  
  	const emitter = new Emittery();
  	const emitStateChange = evt => {
  		if (!finished) {
  			emitter.emit('stateChange', Object.assign(evt, {testFile: file}));
  		}
  	};
  
  	options = {
  		baseDir: process.cwd(),
  		file,
  		forkId,
  		...options
  	};
  
  	const {worker, postMessage, close} = createWorker(options, execArgv);
  	worker.stdout.on('data', chunk => {
  		emitStateChange({type: 'worker-stdout', chunk});
  	});
  
  	worker.stderr.on('data', chunk => {
  		emitStateChange({type: 'worker-stderr', chunk});
  	});
  
  	let forcedExit = false;
  	const send = evt => {
  		if (!finished && !forcedExit) {
  			postMessage({ava: evt});
  		}
  	};
  
  	const promise = new Promise(resolve => {
  		const finish = () => {
  			finished = true;
  			resolve();
  		};
  
  		worker.on('message', message => {
  			if (!message.ava) {
  				return;
  			}
  
  			switch (message.ava.type) {
  				case 'ready-for-options':
  					send({type: 'options', options});
  					break;
  				case 'shared-worker-connect': {
  					const {channelId, filename, initialData, port} = message.ava;
  					emitter.emit('connectSharedWorker', {
  						filename,
  						initialData,
  						port,
  						signalError() {
  							send({type: 'shared-worker-error', channelId});
  						}
  					});
  					break;
  				}
  
  				case 'ping':
  					send({type: 'pong'});
  					break;
  				default:
  					emitStateChange(message.ava);
  			}
  		});
  
  		worker.on('error', error => {
  			emitStateChange({type: 'worker-failed', err: error});
  			finish();
  		});
  
  		worker.on('exit', (code, signal) => {
  			if (forcedExit) {
  				emitStateChange({type: 'worker-finished', forcedExit});
  			} else if (code > 0) {
  				emitStateChange({type: 'worker-failed', nonZeroExitCode: code});
  			} else if (code === null && signal) {
  				emitStateChange({type: 'worker-failed', signal});
  			} else {
  				emitStateChange({type: 'worker-finished', forcedExit});
  			}
  
  			finish();
  		});
  	});
  
  	return {
  		file,
  		forkId,
  		promise,
  
  		exit() {
  			forcedExit = true;
  			close();
  		},
  
  		notifyOfPeerFailure() {
  			send({type: 'peer-failed'});
  		},
  
  		onConnectSharedWorker(listener) {
  			return emitter.on('connectSharedWorker', listener);
  		},
  
  		onStateChange(listener) {
  			return emitter.on('stateChange', listener);
  		}
  	};
  };
  