function composeAppsConfigFromAliased(opts) {
  /* @type {Record<string, Detox.DetoxAppConfig>} */
  const result = {};
  const { configurationName, errorComposer, deviceConfig, globalConfig, localConfig } = opts;

  const isBuiltinDevice = Boolean(deviceAppTypes[deviceConfig.type]);
  if (localConfig.app == null && localConfig.apps == null) {
    if (isBuiltinDevice) {
      throw errorComposer.noAppIsDefined(deviceConfig.type);
    } else {
      return result;
    }
  }

  if (localConfig.app != null && localConfig.apps != null) {
    throw errorComposer.ambiguousAppAndApps();
  }

  if (localConfig.app && Array.isArray(localConfig.app)) {
    throw errorComposer.multipleAppsConfigArrayTypo();
  }

  if (localConfig.apps && !Array.isArray(localConfig.apps)) {
    throw errorComposer.multipleAppsConfigShouldBeArray();
  }

  const appPathsMap = new Map();
  const preliminaryAppPaths = Array.isArray(localConfig.apps)
    ? localConfig.apps.map((_alias, index) => ['configurations', configurationName, 'apps', index])
    : [['configurations', configurationName, 'app']];

  for (const maybeAppPath of preliminaryAppPaths) {
    const maybeAlias = _.get(globalConfig, maybeAppPath);
    const isAlias = _.isString(maybeAlias);
    const appPath = isAlias
      ? ['apps', maybeAlias]
      : maybeAppPath;

    const appConfig = _.get(globalConfig, appPath);
    if (_.isEmpty(appConfig)) {
      if (isAlias) {
        if (_.size(globalConfig.apps) > 0) {
          throw errorComposer.cantResolveAppAlias(maybeAlias);
        } else {
          throw errorComposer.thereAreNoAppConfigs(maybeAlias);
        }
      } else {
        throw errorComposer.appConfigIsUndefined(appPath);
      }
    }

    const appName = appConfig.name || 'default';
    appPathsMap.set(appConfig, appPath);

    validateAppConfig({
      errorComposer,
      deviceConfig,
      appConfig,
      appPath
    });

    if (!result[appName]) {
      result[appName] = appConfig;
    } else {
      throw opts.errorComposer.duplicateAppConfig({
        appPath,
        appName: appConfig.name,
        preExistingAppPath: appPathsMap.get(result[appName]),
      });
    }
  }

  return _.mapValues(result, value => _.clone(value));
}
@param {DetoxConfigErrorComposer} opts.errorComposer
@param {string} opts.configurationName
@param {Detox.DetoxDeviceConfig} opts.deviceConfig
@param {Detox.DetoxConfig} opts.globalConfig
@param {Detox.DetoxConfiguration} opts.localConfig
@returns {Record<string, Detox.DetoxAppConfig>}