function runJsCodeshift(file, {dryRun, verbose, yes, keep, grep}) {
  return new Promise((resolve, reject) => {
    let results = {
      /** A set of fixture paths that were converted. */
      fixturesToCleanup: new Set(),
      /** A set of test names that were modified. */
      testsToVerify: new Set(),
    };

    let args = ['--run-in-band', '--parser=flow'];
    if (dryRun) args.push('--dry');
    if (verbose) args.push('--verbose=2');
    if (yes) args.push('--yes');
    if (keep?.length) args = args.concat(keep.map(k => `--keep "${k}"`));
    if (grep?.length) args = args.concat(grep.map(g => `--grep "${g}"`));
    args.push(`--transform=${__filename}`);
    args = args.concat(file);

    if (verbose) {
      console.log(chalk.dim(`jscodeshift ${args.join(' ')}`));
    }

    const jscodeshift = spawn('jscodeshift', args, {
      env: {FORCE_COLOR: 'true', ...process.env},
      stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
      shell: true,
    });

    jscodeshift.on('message', event => {
      switch (event.type) {
        case 'fixture': {
          results.fixturesToCleanup.add(event.fixturePath);
          break;
        }
        case 'test': {
          results.testsToVerify.add(event.testName);
          break;
        }
        default: {
          console.error(`Unknown event type: ${event.type}`);
        }
      }
    });

    jscodeshift.on('close', code => {
      if (code !== 0) {
        reject(new Error(`Exit code ${code}`));
      } else {
        resolve(results);
      }
    });
  });
}
Runs jscodeshift to apply `transform` to `file`.
Collects the results of the transform and resolves to them when complete.