function composeRunnerConfig(opts) {
  const globalConfig = adaptLegacyRunnerConfig(opts.globalConfig);
  if (globalConfig != null && typeof globalConfig !== 'object') {
    throw opts.errorComposer.invalidTestRunnerProperty(true);
  }

  const localConfig = opts.localConfig.testRunner;
  if (localConfig != null && typeof localConfig !== 'object') {
    throw opts.errorComposer.invalidTestRunnerProperty(false);
  }

  const cliConfig = opts.cliConfig;

  /** @type {Detox.DetoxTestRunnerConfig} */
  const merged = _.merge(
    {
      retries: 0,
      inspectBrk: inspectBrkHookDefault,
      forwardEnv: false,
      detached: false,
      bail: false,
      noRetryArgs: ['shard'],
      jest: {
        setupTimeout: 300000,
        teardownTimeout: 30000,
        retryAfterCircusRetries: false,
        reportSpecs: undefined,
        reportWorkerAssign: true,
      },
      args: {
        $0: 'jest',
        _: [],
      },
    },
    globalConfig,
    localConfig,
    cliConfig.retries != null ? { retries: cliConfig.retries } : null,
    cliConfig.jestReportSpecs != null ? { jest: { reportSpecs: cliConfig.jestReportSpecs } } : null,
    {
      args: _.omitBy(opts.testRunnerArgv, hasEmptyPositionalArgs)
    }
  );

  if (typeof merged.inspectBrk === 'function') {
    if (cliConfig.inspectBrk) {
      merged.detached = false;
      merged.forwardEnv = true;
      merged.retries = 0;
      merged.inspectBrk(merged);
    }

    delete merged.inspectBrk;
  }

  return merged;
}
@param {object} opts
@param {Detox.DetoxConfig} opts.globalConfig
@param {Detox.DetoxConfiguration} opts.localConfig
@param {DetoxInternals.CLIConfig} opts.cliConfig
@param {Record<string, any>} opts.testRunnerArgv
@param {import('../errors/DetoxConfigErrorComposer')} opts.errorComposer
@returns {Detox.DetoxTestRunnerConfig} opts.testRunnerArgv