"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

function _xdl() {
  const data = require("@expo/xdl");

  _xdl = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

function _delayAsync() {
  const data = _interopRequireDefault(require("delay-async"));

  _delayAsync = function () {
    return data;
  };

  return data;
}

function _fp() {
  const data = _interopRequireDefault(require("lodash/fp"));

  _fp = function () {
    return data;
  };

  return data;
}

function _get() {
  const data = _interopRequireDefault(require("lodash/get"));

  _get = function () {
    return data;
  };

  return data;
}

function _ora() {
  const data = _interopRequireDefault(require("ora"));

  _ora = function () {
    return data;
  };

  return data;
}

function _semver() {
  const data = _interopRequireDefault(require("semver"));

  _semver = function () {
    return data;
  };

  return data;
}

function UrlUtils() {
  const data = _interopRequireWildcard(require("../utils/url"));

  UrlUtils = function () {
    return data;
  };

  return data;
}

function _log() {
  const data = _interopRequireDefault(require("../../log"));

  _log = function () {
    return data;
  };

  return data;
}

function _publish() {
  const data = require("../publish");

  _publish = function () {
    return data;
  };

  return data;
}

function _BuildError() {
  const data = _interopRequireDefault(require("./BuildError"));

  _BuildError = function () {
    return data;
  };

  return data;
}

function _prompt() {
  const data = _interopRequireDefault(require("../../prompt"));

  _prompt = function () {
    return data;
  };

  return data;
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

const secondsToMilliseconds = seconds => seconds * 1000;

class BaseBuilder {
  constructor(projectDir, options = {}) {
    _defineProperty(this, "projectDir", '');

    _defineProperty(this, "options", {
      wait: true,
      clearCredentials: false,
      releaseChannel: 'default',
      publish: false
    });

    _defineProperty(this, "manifest", {});

    this.projectDir = projectDir;
    this.options = options;
  }

  async command() {
    try {
      await this.prepareProjectInfo();
      await this.run();
    } catch (e) {
      if (!(e instanceof _BuildError().default)) {
        throw e;
      } else {
        _log().default.error(e.message);

        process.exit(1);
      }
    }
  }

  async commandCheckStatus() {
    try {
      await this.prepareProjectInfo();
      await this.checkStatus();
    } catch (e) {
      if (!(e instanceof _BuildError().default)) {
        throw e;
      } else {
        _log().default.error(e.message);

        process.exit(1);
      }
    }
  }

  async prepareProjectInfo() {
    // always use local json to unify behaviour between regular apps and self hosted ones
    const {
      exp
    } = await _xdl().ProjectUtils.readConfigJsonAsync(this.projectDir);
    this.manifest = exp;
    this.user = await _xdl().UserManager.ensureLoggedInAsync();
    await this.checkProjectConfig();
  }

  async checkProjectConfig() {
    if (this.manifest.isDetached) {
      _log().default.error(`'expo build:${this.platform()}' is not supported for detached projects.`);

      process.exit(1);
    } // Warn user if building a project using the next deprecated SDK version


    let oldestSupportedMajorVersion = await _xdl().Versions.oldestSupportedMajorVersionAsync();

    if (_semver().default.major(this.manifest.sdkVersion) === oldestSupportedMajorVersion) {
      let {
        version
      } = await _xdl().Versions.newestSdkVersionAsync();

      _log().default.warn(`\nSDK${oldestSupportedMajorVersion} will be ${_chalk().default.bold('deprecated')} next! We recommend upgrading versions, ideally to the latest (SDK${_semver().default.major(version)}), so you can continue to build new binaries of your app and develop in the Expo Client.\n`);
    }
  }

  async checkForBuildInProgress() {
    (0, _log().default)('Checking if there is a build in progress...\n');
    const buildStatus = await _xdl().Project.buildAsync(this.projectDir, {
      mode: 'status',
      platform: this.platform(),
      current: true,
      releaseChannel: this.options.releaseChannel,
      publicUrl: this.options.publicUrl,
      sdkVersion: this.manifest.sdkVersion
    });

    if (buildStatus.jobs && buildStatus.jobs.length) {
      throw new (_BuildError().default)('Cannot start a new build, as there is already an in-progress build.');
    }
  }

  async checkStatus(platform = 'all') {
    (0, _log().default)('Fetching build history...\n');
    const buildStatus = await _xdl().Project.buildAsync(this.projectDir, {
      mode: 'status',
      platform,
      current: false,
      releaseChannel: this.options.releaseChannel
    });

    if (buildStatus.err) {
      throw new Error('Error getting current build status for this project.');
    }

    if (!(buildStatus.jobs && buildStatus.jobs.length)) {
      (0, _log().default)('No currently active or previous builds for this project.');
      return;
    }

    this.logBuildStatuses(buildStatus);
  }

  async checkStatusBeforeBuild() {
    (0, _log().default)('Checking if this build already exists...\n');
    const reuseStatus = await _xdl().Project.findReusableBuildAsync(this.options.releaseChannel, this.platform(), this.manifest.sdkVersion, this.manifest.slug);

    if (reuseStatus.canReuse) {
      _log().default.warn(`Did you know that Expo provides over-the-air updates?
Please see the docs (${_chalk().default.underline('https://docs.expo.io/versions/latest/guides/configuring-ota-updates/')}) and check if you can use them instead of building your app binaries again.`);

      _log().default.warn(`There were no new changes from the last build, you can download that build from here: ${_chalk().default.underline(reuseStatus.downloadUrl)}`);

      let questions = [{
        type: 'confirm',
        name: 'confirm',
        message: 'Do you want to build app anyway?'
      }];
      const answers = await (0, _prompt().default)(questions);

      if (!answers.confirm) {
        (0, _log().default)('Stopping the build process');
        process.exit(0);
      }
    }
  }

  logBuildStatuses(buildStatus) {
    _log().default.raw();

    (0, _log().default)('=================');
    (0, _log().default)(' Builds Statuses ');
    (0, _log().default)('=================\n');
    buildStatus.jobs.forEach((job, i) => {
      let platform, packageExtension;

      if (job.platform === 'ios') {
        platform = 'iOS';
        packageExtension = 'IPA';
      } else {
        platform = 'Android';
        packageExtension = 'APK';
      }

      (0, _log().default)(`### ${i} | ${platform} | ${UrlUtils().constructBuildLogsUrl(job.id)} ###`);
      const hasPriorityBuilds = buildStatus.numberOfRemainingPriorityBuilds > 0 || buildStatus.hasUnlimitedPriorityBuilds;
      const shouldShowUpgradeInfo = !hasPriorityBuilds && i === 0 && job.priority === 'normal' && buildStatus.canPurchasePriorityBuilds;
      let status;

      switch (job.status) {
        case 'pending':
        case 'sent-to-queue':
          status = `Build waiting in queue...\nQueue length: ${_chalk().default.underline(UrlUtils().constructTurtleStatusUrl())}`;

          if (shouldShowUpgradeInfo) {
            status += `\nWant to wait less? Get priority builds at ${_chalk().default.underline('https://expo.io/settings/billing')}.`;
          }

          break;

        case 'started':
          status = 'Build started...';
          break;

        case 'in-progress':
          status = 'Build in progress...';

          if (shouldShowUpgradeInfo) {
            status += `\nWant to wait less? Get priority builds at ${_chalk().default.underline('https://expo.io/settings/billing')}.`;
          }

          break;

        case 'finished':
          status = 'Build finished.';

          if (shouldShowUpgradeInfo) {
            status += `\nLooks like this build could have been faster.\nRead more about priority builds at ${_chalk().default.underline('https://expo.io/settings/billing')}.`;
          }

          break;

        case 'errored':
          status = 'There was an error with this build.';

          if (job.id) {
            status += `

When requesting support, please provide this build ID:

${job.id}
`;
          }

          break;

        default:
          status = '';
          break;
      }

      (0, _log().default)(status);

      if (job.status === 'finished') {
        if (job.artifacts) {
          (0, _log().default)(`${packageExtension}: ${job.artifacts.url}`);
        } else {
          (0, _log().default)(`Problem getting ${packageExtension} URL. Please try to build again.`);
        }
      }

      (0, _log().default)();
    });
  }

  async ensureReleaseExists() {
    if (this.options.publish) {
      const {
        ids,
        url,
        err
      } = await (0, _publish().action)(this.projectDir, { ...this.options,
        platform: this.platform(),
        duringBuild: true
      });

      if (err) {
        throw new (_BuildError().default)(`No url was returned from publish. Please try again.\n${err}`);
      } else if (!url || url === '') {
        throw new (_BuildError().default)('No url was returned from publish. Please try again.');
      }

      return ids;
    } else {
      (0, _log().default)('Looking for releases...');
      const release = await _xdl().Project.getLatestReleaseAsync(this.projectDir, {
        releaseChannel: this.options.releaseChannel,
        platform: this.platform()
      });

      if (!release) {
        throw new (_BuildError().default)('No releases found. Please create one using `expo publish` first.');
      }

      (0, _log().default)(`Using existing release on channel "${release.channel}":\n` + `publicationId: ${release.publicationId}\n  publishedTime: ${release.publishedTime}`);
      return [release.publicationId];
    }
  }

  async wait(buildId, {
    timeout = 1200,
    interval = 30,
    publicUrl
  } = {}) {
    (0, _log().default)(`Waiting for build to complete. You can press Ctrl+C to exit.`);
    let spinner = (0, _ora().default)().start();
    let time = new Date().getTime();
    const endTime = time + secondsToMilliseconds(timeout);

    while (time <= endTime) {
      const res = await _xdl().Project.buildAsync(this.projectDir, {
        current: false,
        mode: 'status',
        ...(publicUrl ? {
          publicUrl
        } : {})
      });

      const job = _fp().default.compose(_fp().default.head, _fp().default.filter(job => buildId && job.id === buildId), _fp().default.getOr([], 'jobs'))(res);

      switch (job.status) {
        case 'finished':
          spinner.succeed('Build finished.');
          return job;

        case 'pending':
        case 'sent-to-queue':
          spinner.text = 'Build queued...';
          break;

        case 'started':
        case 'in-progress':
          spinner.text = 'Build in progress...';
          break;

        case 'errored':
          spinner.fail('Build failed.');
          throw new (_BuildError().default)(`Standalone build failed!`);

        default:
          spinner.warn('Unknown status.');
          throw new (_BuildError().default)(`Unknown status: ${job.status} - aborting!`);
      }

      time = new Date().getTime();
      await (0, _delayAsync().default)(secondsToMilliseconds(interval));
    }

    spinner.warn('Timed out.');
    throw new (_BuildError().default)('Timeout reached! Project is taking longer than expected to finish building, aborting wait...');
  }

  async build(expIds) {
    const {
      publicUrl
    } = this.options;
    const platform = this.platform();
    const bundleIdentifier = (0, _get().default)(this.manifest, 'ios.bundleIdentifier');
    let opts = {
      mode: 'create',
      expIds,
      platform,
      releaseChannel: this.options.releaseChannel,
      ...(publicUrl ? {
        publicUrl
      } : {})
    };

    if (platform === 'ios') {
      opts = { ...opts,
        type: this.options.type,
        bundleIdentifier
      };
    } else if (platform === 'android') {
      opts = { ...opts,
        type: this.options.type
      };
    } // call out to build api here with url


    const {
      id: buildId,
      priority,
      canPurchasePriorityBuilds
    } = await _xdl().Project.buildAsync(this.projectDir, opts);
    (0, _log().default)('Build started, it may take a few minutes to complete.');
    (0, _log().default)(`You can check the queue length at ${_chalk().default.underline(UrlUtils().constructTurtleStatusUrl())}\n`);

    if (priority === 'normal' && canPurchasePriorityBuilds) {
      (0, _log().default)('You can make this faster. 🐢\nGet priority builds at: https://expo.io/settings/billing\n');
    }

    if (buildId) {
      (0, _log().default)(`You can monitor the build at\n\n ${_chalk().default.underline(UrlUtils().constructBuildLogsUrl(buildId))}\n`);
    }

    if (this.options.wait) {
      const waitOpts = publicUrl ? {
        publicUrl
      } : {};
      const completedJob = await this.wait(buildId, waitOpts);
      const artifactUrl = completedJob.artifactId ? UrlUtils().constructArtifactUrl(completedJob.artifactId) : completedJob.artifacts.url;
      (0, _log().default)(`${_chalk().default.green('Successfully built standalone app:')} ${_chalk().default.underline(artifactUrl)}`);
    } else {
      (0, _log().default)('Alternatively, run `expo build:status` to monitor it from the command line.');
    }
  }

  platform() {
    return 'all';
  }

}

exports.default = BaseBuilder;
//# sourceMappingURL=../../__sourcemaps__/commands/build/BaseBuilder.js.map