validate = async (options, releaseInfo) => {
  if (
    options.mode !== 'get' &&
    options.mode !== 'run' &&
    options.mode !== 'build'
  ) {
    throw new Error(
      `Unknown mode ${options.mode}. Expected "get", "run" or "build".`,
    );
  }
  if (typeof releaseInfo === 'undefined') {
    throw new Error(
      'Either the specific version info does not exist or the version manifest itself does not exist. In case of the latter, please check your internet connection and try again later.',
    );
  }
  if (!releaseInfo.flavors.includes(options.flavor)) {
    throw new Error(
      `${options.flavor} flavor is not supported by this download server.`,
    );
  }
  if (
    options.platform &&
    options.arch &&
    !releaseInfo.files.includes(`${options.platform}-${options.arch}`)
  ) {
    throw new Error(
      `Platform ${options.platform} and architecture ${options.arch} is not supported by this download server.`,
    );
  }
  if (typeof options.downloadUrl === 'string' && !options.downloadUrl.startsWith('http') && !options.downloadUrl.startsWith('file')) {
    throw new Error('Expected options.downloadUrl to be a string and starts with `http` or `file`.');
  }
  if (typeof options.manifestUrl === 'string' && !options.manifestUrl.startsWith('http') && !options.manifestUrl.startsWith('file')) {
    throw new Error('Expected options.manifestUrl to be a string and starts with `http` or `file`.');
  }
  if (typeof options.cacheDir !== 'string') {
    throw new Error('Expected options.cacheDir to be a string. Got ' + typeof options.cacheDir);
  }

  if (typeof options.cache !== 'boolean') {
    throw new Error(
      'Expected options.cache to be a boolean. Got ' + typeof options.cache,
    );
  }
  if (typeof options.ffmpeg !== 'boolean') {
    throw new Error(
      'Expected options.ffmpeg to be a boolean. Got ' + typeof options.ffmpeg,
    );
  }

  if (
    options.logLevel !== 'error' &&
    options.logLevel !== 'warn' &&
    options.logLevel !== 'info' &&
    options.logLevel !== 'debug'
  ) {
    throw new Error(
      'Expected options.logLevel to be \'error\', \'warn\', \'info\' or \'debug\'. Got ' +
      options.logLevel,
    );
  }

  if (typeof options.shaSum !== 'boolean') {
    throw new Error(
      'Expected options.shaSum to be a boolean. Got ' + typeof options.shaSum,
    );
  }

  if (options.mode === 'get') {
    return undefined;
  }
  if (typeof options.srcDir !== 'string' && !Array.isArray(options.srcDir)) {
    throw new Error('Expected options.srcDir to be a string or Array<string>. Got ' + typeof options.srcDir);
  }
  if (!Array.isArray(options.argv)) {
    throw new Error(
      'Expected options.argv to be an array. Got ' + typeof options.argv,
    );
  }
  if (typeof options.glob !== 'boolean') {
    throw new Error(
      'Expected options.glob to be a boolean. Got ' + typeof options.glob,
    );
  }

  if (options.mode === 'run') {
    return undefined;
  }

  if (typeof options.outDir !== 'string') {
    throw new Error('Expected options.outDir to be a string. Got ' + typeof options.outDir);
  }

  if (
    typeof options.managedManifest !== 'boolean' &&
    typeof options.managedManifest !== 'object' &&
    typeof options.managedManifest !== 'string'
  ) {
    throw new Error(
      'Expected options.managedManifest to be a boolean, object or string. Got ' +
      typeof options.managedManifest,
    );
  }

  if (typeof options.managedManifest === 'object') {
    if (options.managedManifest.name === undefined) {
      throw new Error('Expected NW.js Manifest to have a `name` property.');
    }
    if (options.managedManifest.main === undefined) {
      throw new Error('Expected NW.js Manifest to have a `main` property.');
    }
  }

  if (typeof options.nativeAddon !== 'boolean') {
    if (typeof options.nativeAddon !== 'boolean' && typeof options.nativeAddon !== 'string') {
      throw new Error('Expected options.nativeAddon to be a boolean or string type. Got ' + typeof options.nativeAddon);
    }

    if (semver.parse(options.version).minor >= '83' && options.nativeAddon !== false) {
      throw new Error('Native addons are not supported for NW.js v0.82.0 and below');
    }

    if (typeof options.zip !== 'boolean' &
      options.zip !== 'zip' &&
      options.zip !== 'tar' &&
      options.zip !== 'tgz') {
      throw new Error('Expected options.zip to be a boolean, `zip`, `tar` or `tgz`. Got ' + typeof options.zip);
    }
  }

  if (options.platform === 'linux') {
    if (options.app.name && typeof options.app.name !== 'string') {
      throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
    }
    if (options.app.genericName && typeof options.app.genericName !== 'string') {
      throw new Error('Expected options.app.genericName to be a string. Got ' + options.app.genericName);
    }
    if (options.app.noDisplay && typeof options.app.noDisplay !== 'boolean') {
      throw new Error('Expected options.app.noDisplay to be a boolean. Got ' + options.app.noDisplay);
    }
    if (options.app.comment && typeof options.app.comment !== 'string') {
      throw new Error('Expected options.app.comment to be a string. Got ' + options.app.comment);
    }
    if (options.app.icon && typeof options.app.icon !== 'string') {
      throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
    }
    if (options.app.hidden && typeof options.app.hidden !== 'string') {
      throw new Error('Expected options.app.hidden to be a string. Got ' + options.app.hidden);
    }
    if (options.app.onlyShowIn && !Array.isArray(options.app.onlyShowIn)) {
      throw new Error('Expected options.app.onlyShowIn to be an Array<string>. Got ' + options.app.onlyShowIn);
    }
    if (options.app.notShowIn && !Array.isArray(options.app.notShowIn)) {
      throw new Error('Expected options.app.notShowIn to be an Array<string>. Got ' + options.app.notShowIn);
    }
    if (options.app.dBusActivatable && typeof options.app.dBusActivatable !== 'boolean') {
      throw new Error('Expected options.app.dBusActivatable to be a boolean. Got ' + options.app.dBusActivatable);
    }
    if (options.app.tryExec && typeof options.app.tryExec !== 'string') {
      throw new Error('Expected options.app.tryExec to be a string. Got ' + options.app.tryExec);
    }
    if (options.app.exec && typeof options.app.exec !== 'string') {
      throw new Error('Expected options.app.exec to be a string. Got ' + options.app.exec);
    }
    if (options.app.path && typeof options.app.path !== 'string') {
      throw new Error('Expected options.app.path to be a string. Got ' + options.app.path);
    }
    if (options.app.terminal && typeof options.app.terminal !== 'boolean') {
      throw new Error('Expected options.app.terminal to be a boolean. Got ' + options.app.terminal);
    }
    if (options.app.actions && !Array.isArray(options.app.actions)) {
      throw new Error('Expected options.app.actions to be a Array<string>. Got ' + options.app.actions);
    }
    if (options.app.mimeType && !Array.isArray(options.app.mimeType)) {
      throw new Error('Expected options.app.mimeType to be a Array<string>. Got ' + options.app.mimeType);
    }
    if (options.app.categories && !Array.isArray(options.app.categories)) {
      throw new Error('Expected options.app.categories to be a Array<string>. Got ' + options.app.categories);
    }
    if (options.app.implements && !Array.isArray(options.app.implements)) {
      throw new Error('Expected options.app.implements to be a Array<string>. Got ' + options.app.implements);
    }
    if (options.app.keywords && !Array.isArray(options.app.keywords)) {
      throw new Error('Expected options.app.keywords to be a Array<string>. Got ' + options.app.keywords);
    }
    if (options.app.startupNotify && typeof options.app.startupNotify !== 'boolean') {
      throw new Error('Expected options.app.startupNotify to be a boolean. Got ' + options.app.startupNotify);
    }
    if (options.app.startupWMClass && typeof options.app.startupWMClass !== 'string') {
      throw new Error('Expected options.app.startupWMClass to be a string. Got ' + options.app.startupWMClass);
    }
    if (options.app.prefersNonDefaultGPU && typeof options.app.prefersNonDefaultGPU !== 'boolean') {
      throw new Error('Expected options.app.prefersNonDefaultGPU to be a boolean. Got ' + options.app.prefersNonDefaultGPU);
    }
    if (options.app.singleMainWindow && typeof options.app.singleMainWindow !== 'string') {
      throw new Error('Expected options.app.singleMainWindow to be a string. Got ' + options.app.singleMainWindow);
    }
  } else if (options.platform === 'osx') {
    if (typeof options.app.name !== 'string') {
      throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
    }
    if (typeof options.app.icon !== 'string') {
      throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
    }
    if (typeof options.app.LSApplicationCategoryType !== 'string') {
      throw new Error('Expected options.app.LSApplicationCategoryType to be a string. Got ' + options.app.LSApplicationCategoryType);
    }
    if (typeof options.app.CFBundleIdentifier !== 'string') {
      throw new Error('Expected options.app.CFBundleIdentifier to be a string. Got ' + options.app.CFBundleIdentifier);
    }
    if (typeof options.app.CFBundleName !== 'string') {
      throw new Error('Expected options.app.CFBundleName to be a string. Got ' + options.app.CFBundleName);
    }
    if (typeof options.app.CFBundleDisplayName !== 'string') {
      throw new Error('Expected options.app.CFBundleDisplayName to be a string. Got ' + options.app.CFBundleDisplayName);
    }
    if (typeof options.app.CFBundleSpokenName !== 'string') {
      throw new Error('Expected options.app.CFBundleSpokenName to be a string. Got ' + options.app.CFBundleSpokenName);
    }
    if (typeof options.app.CFBundleVersion !== 'string') {
      throw new Error('Expected options.app.CFBundleVersion to be a string. Got ' + options.app.CFBundleVersion);
    }
    if (typeof options.app.CFBundleShortVersionString !== 'string') {
      throw new Error('Expected options.app.CFBundleShortVersionString to be a string. Got ' + options.app.CFBundleShortVersionString);
    }
    if (typeof options.app.NSHumanReadableCopyright !== 'string') {
      throw new Error('Expected options.app.NSHumanReadableCopyright to be a string. Got ' + options.app.NSHumanReadableCopyright);
    }
    if (typeof options.app.NSLocalNetworkUsageDescription !== 'string') {
      throw new Error('Expected options.app.NSLocalNetworkUsageDescription to be a string. Got ' + options.app.NSLocalNetworkUsageDescription);
    }
  } else {
    if (typeof options.app.name !== 'string') {
      throw new Error('Expected options.app.name to be a string. Got ' + options.app.name);
    }
    if (typeof options.app.icon !== 'string') {
      throw new Error('Expected options.app.icon to be a string. Got ' + options.app.icon);
    }
    if (typeof options.app.version !== 'string') {
      throw new Error('Expected options.app.version to be a string. Got ' + options.app.version);
    }
    if (options.app.comments && typeof options.app.comments !== 'string') {
      throw new Error('Expected options.app.comments to be a string. Got ' + options.app.comments);
    }
    if (options.app.company && typeof options.app.company !== 'string') {
      throw new Error('Expected options.app.company to be a string. Got ' + options.app.company);
    }
    if (options.app.fileDescription && typeof options.app.fileDescription !== 'string') {
      throw new Error('Expected options.app.fileDescription to be a string. Got ' + options.app.fileDescription);
    }
    if (options.app.fileVersion && typeof options.app.fileVersion !== 'string') {
      throw new Error('Expected options.app.fileVersion to be a string. Got ' + options.app.fileVersion);
    }
    if (options.app.internalName && typeof options.app.internalName !== 'string') {
      throw new Error('Expected options.app.internalName to be a string. Got ' + options.app.internalName);
    }
    if (options.app.legalCopyright && options.app.legalCopyright && typeof options.app.legalCopyright !== 'string') {
      throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright);
    }
    if (options.app.legalTrademark && typeof options.app.legalTrademark !== 'string') {
      throw new Error('Expected options.app.legalTrademark to be a string. Got ' + options.app.legalTrademark);
    }
    if (options.app.originalFilename && typeof options.app.originalFilename !== 'string') {
      throw new Error('Expected options.app.originalFilename to be a string. Got ' + options.app.originalFilename);
    }
    if (options.app.privateBuild && typeof options.app.privateBuild !== 'string') {
      throw new Error('Expected options.app.privateBuild to be a string. Got ' + options.app.privateBuild);
    }
    if (options.app.productName && typeof options.app.productName !== 'string') {
      throw new Error('Expected options.app.productName to be a string. Got ' + options.app.productName);
    }
    if (options.app.productVersion && typeof options.app.productVersion !== 'string') {
      throw new Error('Expected options.app.productVersion to be a string. Got ' + options.app.productVersion);
    }
    if (options.app.specialBuild && options.app.specialBuild && typeof options.app.specialBuild !== 'string') {
      throw new Error('Expected options.app.specialBuild to be a string. Got ' + options.app.specialBuild);
    }
    if (options.app.legalCopyright && typeof options.app.legalCopyright !== 'string') {
      throw new Error('Expected options.app.legalCopyright to be a string. Got ' + options.app.legalCopyright);
    }
    if (typeof options.app.languageCode !== 'number') {
      throw new Error('Expected options.app.languageCode to be a number. Got ' + options.app.languageCode);
    }
  }
  return undefined;
}
Validate options.
@param  {import("../index.js").Options} options      Options
@param  {object}                        releaseInfo  Version specific NW release info
@returns {Promise<undefined>}                         Return undefined if options are valid
@throws {Error}                                         Throw error if options are invalid