"use strict";

var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard");

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

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

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function RA() {
  const data = _interopRequireWildcard(require("ramda-adjunct"));

  RA = function () {
    return data;
  };

  return data;
}

function _graphlib() {
  const data = _interopRequireDefault(require("graphlib"));

  _graphlib = function () {
    return data;
  };

  return data;
}

function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));

  _pMapSeries = function () {
    return data;
  };

  return data;
}

function _loaderMessages() {
  const data = require("../../cli/loader/loader-messages");

  _loaderMessages = function () {
    return data;
  };

  return data;
}

function _loader() {
  const data = _interopRequireDefault(require("../../cli/loader"));

  _loader = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireDefault(require("../../logger/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

function _analytics() {
  const data = require("../../analytics/analytics");

  _analytics = function () {
    return data;
  };

  return data;
}

function _exceptions() {
  const data = require("../../consumer/exceptions");

  _exceptions = function () {
    return data;
  };

  return data;
}

function _utils() {
  const data = require("../../utils");

  _utils = function () {
    return data;
  };

  return data;
}

function _exceptions2() {
  const data = require("../exceptions");

  _exceptions2 = function () {
    return data;
  };

  return data;
}

function _bitId() {
  const data = require("../../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

function _flattenDependencies() {
  const data = require("../flatten-dependencies");

  _flattenDependencies = function () {
    return data;
  };

  return data;
}

function _validationError() {
  const data = _interopRequireDefault(require("../../error/validation-error"));

  _validationError = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _generalError() {
  const data = _interopRequireDefault(require("../../error/general-error"));

  _generalError = function () {
    return data;
  };

  return data;
}

function _dependencies() {
  const data = require("../../consumer/component/dependencies");

  _dependencies = function () {
    return data;
  };

  return data;
}

function _autoTag() {
  const data = require("./auto-tag");

  _autoTag = function () {
    return data;
  };

  return data;
}

function _scopeComponentsImporter() {
  const data = _interopRequireDefault(require("./scope-components-importer"));

  _scopeComponentsImporter = function () {
    return data;
  };

  return data;
}

function _componentsGraph() {
  const data = require("../graph/components-graph");

  _componentsGraph = function () {
    return data;
  };

  return data;
}

function getFlattenedDependencies(_x, _x2, _x3, _x4, _x5, _x6) {
  return _getFlattenedDependencies.apply(this, arguments);
}

function _getFlattenedDependencies() {
  _getFlattenedDependencies = (0, _bluebird().coroutine)(function* (scope, component, graph, cache, notFoundDependencies, prodGraph) {
    const id = component.id.toString();
    const edges = getEdges(graph, id);
    if (!edges) return new (_bitId().BitIds)();
    const dependencies = getEdgesWithProdGraph(prodGraph, edges);
    if (!dependencies.length) return new (_bitId().BitIds)();

    const flattenDependency =
    /*#__PURE__*/
    function () {
      var _ref3 = (0, _bluebird().coroutine)(function* (dependency) {
        if (cache[dependency]) return cache[dependency]; // $FlowFixMe if graph doesn't have the node, prodGraph must have it

        const dependencyBitId = graph.node(dependency) || prodGraph.node(dependency);
        let versionDependencies;

        const scopeComponentsImporter = _scopeComponentsImporter().default.getInstance(scope);

        if (notFoundDependencies.has(dependencyBitId)) return [dependencyBitId];

        try {
          versionDependencies = yield scopeComponentsImporter.importDependencies(_bitId().BitIds.fromArray([dependencyBitId]));
        } catch (err) {
          if (err instanceof _exceptions2().DependencyNotFound) {
            notFoundDependencies.push(dependencyBitId);
            throwWhenDepNotIncluded(component.id, dependencyBitId);
            return [dependencyBitId];
          }

          throw err;
        }

        const flattenedDependencies = yield (0, _flattenDependencies().flattenDependencyIds)(versionDependencies, scope.objects); // Store the flatten dependencies in cache

        cache[dependency] = flattenedDependencies;
        return flattenedDependencies;
      });

      return function flattenDependency(_x14) {
        return _ref3.apply(this, arguments);
      };
    }();

    const flattened = yield (0, _pMapSeries().default)(dependencies, flattenDependency);

    const flattenedUnique = _bitId().BitIds.uniqFromArray(_ramda().default.flatten(flattened)); // when a component has cycle dependencies, the flattenedDependencies contains the component itself. remove it.


    return flattenedUnique.removeIfExistWithoutVersion(component.id);
  });
  return _getFlattenedDependencies.apply(this, arguments);
}

function throwWhenDepNotIncluded(componentId, dependencyId) {
  if (!dependencyId.hasScope() && !dependencyId.hasVersion()) {
    throw new (_generalError().default)(`fatal: "${componentId.toString()}" has a dependency "${dependencyId.toString()}".
this dependency was not included in the tag command.`);
  }
}

function getEdges(graph, id) {
  if (!graph.hasNode(id)) return null;

  const edges = _graphlib().default.alg.preorder(graph, id);

  return _ramda().default.tail(edges); // the first item is the component itself
}
/**
 * for non-prod files, such as test files, we're interested also with its prod dependency.
 * for example, a test file foo.spec.js of component 'foo', requires bar.js from component
 * 'bar'. 'bar.js' requires 'baz.js' from component 'baz'.
 * when calculating the edges of foo.spec.js by devGraph only, we'll get bar.js but not
 * baz.js because the relationship between bar and baz are set on prodGraph only.
 * @see dev-dependencies.e2e, 'dev-dependency that requires prod-dependency' case.
 */


function getEdgesWithProdGraph(prodGraph, dependencies) {
  if (!prodGraph) return dependencies; // $FlowFixMe

  const prodDependencies = _ramda().default.flatten(dependencies.map(dependency => getEdges(prodGraph, dependency))).filter(x => x);

  return _ramda().default.uniq([...dependencies, ...prodDependencies]);
}

function updateDependenciesVersions(componentsToTag) {
  const updateDependencyVersion = dependency => {
    const foundDependency = componentsToTag.find(component => component.id.isEqualWithoutVersion(dependency.id));
    if (foundDependency) dependency.id = dependency.id.changeVersion(foundDependency.version);
  };

  componentsToTag.forEach(oneComponentToTag => {
    oneComponentToTag.getAllDependencies().forEach(dependency => updateDependencyVersion(dependency));
  });
}

function setFutureVersions(_x7, _x8, _x9, _x10) {
  return _setFutureVersions.apply(this, arguments);
}
/**
 * make sure the originallySharedDir was added before saving the component. also, make sure it was
 * not added twice.
 * we need three objects for this:
 * 1) component.pendingVersion => version pending to be saved in the filesystem. we want to make sure it has the added sharedDir.
 * 2) component.componentFromModel => previous version of the component. it has the original sharedDir.
 * 3) component.componentMap => current paths in the filesystem, which don't have the sharedDir.
 *
 * The component may be changed from the componentFromModel. The files may be removed and added and
 * new files may added, so we can't compare the files of componentFromModel to component.
 *
 * What we can do is calculating the sharedDir from component.componentFromModel
 * then, make sure that calculatedSharedDir + pathFromComponentMap === component.pendingVersion
 *
 * Also, make sure that the wrapDir has been removed
 */


function _setFutureVersions() {
  _setFutureVersions = (0, _bluebird().coroutine)(function* (componentsToTag, scope, releaseType, exactVersion) {
    yield Promise.all(componentsToTag.map(
    /*#__PURE__*/
    function () {
      var _ref4 = (0, _bluebird().coroutine)(function* (componentToTag) {
        const modelComponent = yield scope.sources.findOrAddComponent(componentToTag);
        const version = modelComponent.getVersionToAdd(releaseType, exactVersion); // $FlowFixMe usedVersion is needed only for this, that's why it's not declared on the instance

        componentToTag.usedVersion = componentToTag.version;
        componentToTag.version = version;
      });

      return function (_x15) {
        return _ref4.apply(this, arguments);
      };
    }()));
  });
  return _setFutureVersions.apply(this, arguments);
}

function validateDirManipulation(components) {
  const throwOnError = (expectedPath, actualPath) => {
    if (expectedPath !== actualPath) {
      throw new (_validationError().default)(`failed validating the component paths with sharedDir, expected path ${expectedPath}, got ${actualPath}`);
    }
  };

  const validateComponent = component => {
    if (!component.componentMap) throw new Error(`componentMap is missing from ${component.id.toString()}`);
    if (!component.componentFromModel) return; // component.componentFromModel.setOriginallySharedDir();

    const sharedDir = component.componentFromModel.originallySharedDir;
    const wrapDir = component.componentFromModel.wrapDir;

    const pathWithSharedDir = pathStr => {
      // $FlowFixMe componentMap is set here
      if (sharedDir && component.componentMap.origin === _constants().COMPONENT_ORIGINS.IMPORTED) {
        return (0, _utils().pathJoinLinux)(sharedDir, pathStr);
      }

      return pathStr;
    };

    const pathWithoutWrapDir = pathStr => {
      if (wrapDir) {
        return pathStr.replace(`${wrapDir}/`, '');
      }

      return pathStr;
    };

    const pathAfterDirManipulation = pathStr => {
      const withoutWrapDir = pathWithoutWrapDir(pathStr);
      return pathWithSharedDir(withoutWrapDir);
    };

    const expectedMainFile = pathAfterDirManipulation(component.componentMap.mainFile);
    throwOnError(expectedMainFile, component.pendingVersion.mainFile); // $FlowFixMe componentMap is set here

    const componentMapFiles = component.componentMap.getAllFilesPaths();
    const componentFiles = component.pendingVersion.files.map(file => file.relativePath);
    componentMapFiles.forEach(file => {
      const expectedFile = pathAfterDirManipulation(file);

      if (!componentFiles.includes(expectedFile)) {
        throw new (_validationError().default)(`failed validating the component paths, expected a file ${expectedFile} to be in ${componentFiles.toString()} array`);
      }
    });
  };

  components.forEach(component => validateComponent(component));
}

var _default =
/*#__PURE__*/
function () {
  var _tagModelComponent = (0, _bluebird().coroutine)(function* ({
    consumerComponents,
    scope,
    message,
    exactVersion,
    releaseType,
    force,
    consumer,
    ignoreNewestVersion = false,
    skipTests = false,
    verbose = false
  }) {
    _loader().default.start(_loaderMessages().BEFORE_IMPORT_PUT_ON_SCOPE);

    const consumerComponentsIdsMap = {}; // Concat and unique all the dependencies from all the components so we will not import
    // the same dependency more then once, it's mainly for performance purpose

    consumerComponents.forEach(consumerComponent => {
      const componentIdString = consumerComponent.id.toString(); // Store it in a map so we can take it easily from the sorted array which contain only the id

      consumerComponentsIdsMap[componentIdString] = consumerComponent;
    });

    const componentsToTag = _ramda().default.values(consumerComponentsIdsMap); // consumerComponents unique


    const componentsToTagIds = componentsToTag.map(c => c.id);
    const componentsToTagIdsLatest = yield scope.latestVersions(componentsToTagIds, false);
    const autoTagCandidates = consumer.potentialComponentsForAutoTagging(componentsToTagIdsLatest); // $FlowFixMe unclear error

    const autoTagComponents = yield (0, _autoTag().getAutoTagPending)(scope, autoTagCandidates, componentsToTagIdsLatest); // scope.toConsumerComponents(autoTaggedCandidates); won't work as it doesn't have the paths according to bitmap

    const autoTagComponentsLoaded = yield consumer.loadComponents(autoTagComponents.map(c => c.toBitId()));
    const autoTagConsumerComponents = autoTagComponentsLoaded.components;
    const componentsToBuildAndTest = componentsToTag.concat(autoTagConsumerComponents); // check for each one of the components whether it is using an old version

    if (!ignoreNewestVersion) {
      const newestVersionsP = componentsToBuildAndTest.map(
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (component) {
          if (component.componentFromModel) {
            // otherwise it's a new component, so this check is irrelevant
            const modelComponent = yield scope.getModelComponentIfExist(component.id);
            if (!modelComponent) throw new (_generalError().default)(`component ${component.id} was not found in the model`);
            const latest = modelComponent.latest();

            if (latest !== component.version) {
              return {
                componentId: component.id.toStringWithoutVersion(),
                currentVersion: component.version,
                latestVersion: latest
              };
            }
          }

          return null;
        });

        return function (_x12) {
          return _ref.apply(this, arguments);
        };
      }());
      const newestVersions = yield Promise.all(newestVersionsP);
      const newestVersionsWithoutEmpty = newestVersions.filter(newest => newest);

      if (!RA().isNilOrEmpty(newestVersionsWithoutEmpty)) {
        throw new (_exceptions().NewerVersionFound)(newestVersionsWithoutEmpty);
      }
    }

    _logger().default.debug('scope.putMany: sequentially build all components');

    _analytics().Analytics.addBreadCrumb('scope.putMany', 'scope.putMany: sequentially build all components');

    yield scope.buildMultiple(componentsToBuildAndTest, consumer, false, verbose);

    _logger().default.debug('scope.putMany: sequentially test all components');

    let testsResults = [];

    if (!skipTests) {
      const testsResultsP = scope.testMultiple({
        components: componentsToBuildAndTest,
        consumer,
        verbose,
        rejectOnFailure: !force
      });

      try {
        testsResults = yield testsResultsP;
      } catch (err) {
        // if force is true, ignore the tests and continue
        if (!force) {
          if (!verbose) throw new (_exceptions().ComponentSpecsFailed)();
          throw err;
        }
      }
    }

    _logger().default.debug('scope.putMany: sequentially persist all components');

    _analytics().Analytics.addBreadCrumb('scope.putMany', 'scope.putMany: sequentially persist all components'); // go through all components and find the future versions for them


    yield setFutureVersions(componentsToTag, scope, releaseType, exactVersion); // go through all dependencies and update their versions

    updateDependenciesVersions(componentsToTag); // build the dependencies graph

    const {
      graphDeps,
      graphDevDeps,
      graphCompilerDeps,
      graphTesterDeps
    } = (0, _componentsGraph().buildComponentsGraph)(componentsToTag);
    const dependenciesCache = {};
    const notFoundDependencies = new (_bitId().BitIds)();

    const persistComponent =
    /*#__PURE__*/
    function () {
      var _ref2 = (0, _bluebird().coroutine)(function* (consumerComponent) {
        let testResult;

        if (!skipTests) {
          testResult = testsResults.find(result => {
            return consumerComponent.id.isEqualWithoutScopeAndVersion(result.componentId);
          });
        }

        const flattenedDependencies = yield getFlattenedDependencies(scope, consumerComponent, graphDeps, dependenciesCache, notFoundDependencies);
        const flattenedDevDependencies = yield getFlattenedDependencies(scope, consumerComponent, graphDevDeps, dependenciesCache, notFoundDependencies, graphDeps);
        const flattenedCompilerDependencies = yield getFlattenedDependencies(scope, consumerComponent, graphCompilerDeps, dependenciesCache, notFoundDependencies, graphDeps);
        const flattenedTesterDependencies = yield getFlattenedDependencies(scope, consumerComponent, graphTesterDeps, dependenciesCache, notFoundDependencies, graphDeps);
        yield scope.sources.addSource({
          source: consumerComponent,
          consumer,
          flattenedDependencies,
          flattenedDevDependencies,
          flattenedCompilerDependencies,
          flattenedTesterDependencies,
          message,
          exactVersion,
          releaseType,
          specsResults: testResult ? testResult.specs : undefined
        });
        return consumerComponent;
      });

      return function persistComponent(_x13) {
        return _ref2.apply(this, arguments);
      };
    }(); // Run the persistence one by one not in parallel!


    _loader().default.start(_loaderMessages().BEFORE_PERSISTING_PUT_ON_SCOPE);

    const taggedComponents = yield (0, _pMapSeries().default)(componentsToTag, consumerComponent => persistComponent(consumerComponent));
    const autoTaggedResults = yield (0, _autoTag().bumpDependenciesVersions)(scope, autoTagCandidates, taggedComponents);
    validateDirManipulation(taggedComponents);
    yield scope.objects.persist();
    return {
      taggedComponents,
      autoTaggedResults
    };
  });

  function tagModelComponent(_x11) {
    return _tagModelComponent.apply(this, arguments);
  }

  return tagModelComponent;
}();

exports.default = _default;