"use strict";

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 _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

function _stringFormat() {
  const data = _interopRequireDefault(require("string-format"));

  _stringFormat = function () {
    return data;
  };

  return data;
}

function _lodash() {
  const data = _interopRequireDefault(require("lodash.assignwith"));

  _lodash = function () {
    return data;
  };

  return data;
}

function _lodash2() {
  const data = _interopRequireDefault(require("lodash.groupby"));

  _lodash2 = function () {
    return data;
  };

  return data;
}

function _lodash3() {
  const data = _interopRequireDefault(require("lodash.unionby"));

  _lodash3 = function () {
    return data;
  };

  return data;
}

function _ignore() {
  const data = _interopRequireDefault(require("ignore"));

  _ignore = function () {
    return data;
  };

  return data;
}

function _arrayDifference() {
  const data = _interopRequireDefault(require("array-difference"));

  _arrayDifference = function () {
    return data;
  };

  return data;
}

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

  _analytics = function () {
    return data;
  };

  return data;
}

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

  _utils = function () {
    return data;
  };

  return data;
}

function _bitMap() {
  const data = _interopRequireDefault(require("../../../consumer/bit-map"));

  _bitMap = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

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

  _exceptions = function () {
    return data;
  };

  return data;
}

function _componentMap() {
  const data = _interopRequireDefault(require("../../bit-map/component-map"));

  _componentMap = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

function _versionShouldBeRemoved() {
  const data = _interopRequireDefault(require("./exceptions/version-should-be-removed"));

  _versionShouldBeRemoved = function () {
    return data;
  };

  return data;
}

function _linkContent() {
  const data = require("../../../links/link-content");

  _linkContent = function () {
    return data;
  };

  return data;
}

function _missingMainFile() {
  const data = _interopRequireDefault(require("../../bit-map/exceptions/missing-main-file"));

  _missingMainFile = function () {
    return data;
  };

  return data;
}

function _missingMainFileMultipleComponents() {
  const data = _interopRequireDefault(require("./exceptions/missing-main-file-multiple-components"));

  _missingMainFileMultipleComponents = function () {
    return data;
  };

  return data;
}

function _pathOutsideConsumer() {
  const data = _interopRequireDefault(require("./exceptions/path-outside-consumer"));

  _pathOutsideConsumer = function () {
    return data;
  };

  return data;
}

function _models() {
  const data = require("../../../scope/models");

  _models = function () {
    return data;
  };

  return data;
}

function _determineMainFile() {
  const data = _interopRequireDefault(require("./determine-main-file"));

  _determineMainFile = function () {
    return data;
  };

  return data;
}

const REGEX_DSL_PATTERN = /{([^}]+)}/g;

class AddComponents {
  // id entered by the user
  // (default = false) replace the files array or only add files.
  constructor(context, addProps) {
    this.alternateCwd = context.alternateCwd;
    this.consumer = context.consumer;
    this.bitMap = this.consumer.bitMap;
    this.componentPaths = this.joinConsumerPathIfNeeded(addProps.componentPaths);
    this.id = addProps.id;
    this.main = addProps.main;
    this.namespace = addProps.namespace;
    this.tests = addProps.tests ? this.joinConsumerPathIfNeeded(addProps.tests) : [];
    this.exclude = addProps.exclude ? this.joinConsumerPathIfNeeded(addProps.exclude) : [];
    this.override = addProps.override;
    this.trackDirFeature = addProps.trackDirFeature;
    this.origin = addProps.origin || _constants().COMPONENT_ORIGINS.AUTHORED;
    this.warnings = {
      alreadyUsed: {},
      emptyDirectory: [],
      existInScope: []
    };
    this.addedComponents = [];
  }

  joinConsumerPathIfNeeded(paths) {
    if (paths.length > 0) {
      if (this.alternateCwd !== undefined && this.alternateCwd !== null) {
        const alternate = this.alternateCwd;
        return paths.map(file => _path().default.join(alternate, file));
      }

      return paths;
    }

    return [];
  }
  /**
   * @param {string[]} files - array of file-paths from which it should search for the dsl patterns.
   * @param {*} filesWithPotentialDsl - array of file-path which may have DSL patterns
   *
   * @returns array of file-paths from 'files' parameter that match the patterns from 'filesWithPotentialDsl' parameter
   */


  getFilesAccordingToDsl(files, filesWithPotentialDsl) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const filesListAllMatches = filesWithPotentialDsl.map(
      /*#__PURE__*/
      function () {
        var _ref = (0, _bluebird().coroutine)(function* (dsl) {
          const filesListMatch = files.map(
          /*#__PURE__*/
          function () {
            var _ref2 = (0, _bluebird().coroutine)(function* (file) {
              const fileInfo = (0, _utils().calculateFileInfo)(file);
              const generatedFile = (0, _stringFormat().default)(dsl, fileInfo);
              const matches = yield (0, _utils().glob)(generatedFile);

              const matchesAfterGitIgnore = _this.gitIgnore.filter(matches);

              return matchesAfterGitIgnore.filter(match => _fsExtra().default.existsSync(match));
            });

            return function (_x2) {
              return _ref2.apply(this, arguments);
            };
          }());
          return Promise.all(filesListMatch);
        });

        return function (_x) {
          return _ref.apply(this, arguments);
        };
      }());

      const filesListFlatten = _ramda().default.flatten((yield Promise.all(filesListAllMatches)));

      const filesListUnique = _ramda().default.uniq(filesListFlatten);

      return filesListUnique.map(file => {
        // when files array has the test file with different letter case, use the one from the file array
        const fileNormalized = (0, _utils().pathNormalizeToLinux)(file);
        const fileWithCorrectCase = files.find(f => f.toLowerCase() === fileNormalized.toLowerCase()) || fileNormalized;

        const relativeToConsumer = _this.consumer.getPathRelativeToConsumer(fileWithCorrectCase);

        return (0, _utils().pathNormalizeToLinux)(relativeToConsumer);
      });
    })();
  }
  /**
   * unsupported files, such as, binary files, don't have link-file. instead, they have a symlink
   * inside the component dir, pointing to the dependency.
   * this methods check whether a file is auto generated for the unsupported files.
   */


  _isGeneratedForUnsupportedFiles(fileRelativePath, componentId, componentMap) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      if ((0, _linkContent().isSupportedExtension)(fileRelativePath)) return false;
      const componentFromModel = yield _this2.consumer.loadComponentFromModelIfExist(componentId);

      if (!componentFromModel) {
        throw new (_generalError().default)(`failed finding ${componentId.toString()} in the model although the component is imported, try running "bit import ${componentId.toString()} --objects" to get the component saved in the model`);
      }

      const dependencies = componentFromModel.getAllDependenciesCloned();
      const sourcePaths = dependencies.getSourcesPaths();
      const sourcePathsRelativeToConsumer = sourcePaths.map(sourcePath => (0, _utils().pathJoinLinux)(componentMap.rootDir, sourcePath));
      return sourcePathsRelativeToConsumer.includes(fileRelativePath);
    })();
  }
  /**
   * for imported component, the package.json in the root directory is a bit-generated file and as
   * such, it should be ignored
   */


  _isPackageJsonOnRootDir(pathRelativeToConsumerRoot, componentMap) {
    if (!componentMap.rootDir || componentMap.origin !== _constants().COMPONENT_ORIGINS.IMPORTED) {
      throw new Error('_isPackageJsonOnRootDir should not get called on non imported components');
    }

    return _path().default.join(componentMap.rootDir, _constants().PACKAGE_JSON) === _path().default.normalize(pathRelativeToConsumerRoot);
  }
  /**
   * imported components might have wrapDir, when they do, files should not be added outside of
   * that wrapDir
   */


  _isOutsideOfWrapDir(pathRelativeToConsumerRoot, componentMap) {
    if (!componentMap.rootDir || componentMap.origin !== _constants().COMPONENT_ORIGINS.IMPORTED) {
      throw new Error('_isOutsideOfWrapDir should not get called on non imported components');
    }

    if (!componentMap.wrapDir) return false;

    const wrapDirRelativeToConsumerRoot = _path().default.join(componentMap.rootDir, componentMap.wrapDir);

    return !_path().default.normalize(pathRelativeToConsumerRoot).startsWith(wrapDirRelativeToConsumerRoot);
  }
  /**
   * Add or update existing (imported and new) component according to bitmap
   * there are 3 options:
   * 1. a user is adding a new component. there is no record for this component in bit.map
   * 2. a user is updating an existing component. there is a record for this component in bit.map
   * 3. some or all the files of this component were previously added as another component-id.
   */


  addOrUpdateComponentInBitMap(component) {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      const consumerPath = _this3.consumer.getPath();

      const parsedBitId = component.componentId;
      const componentFromScope = yield _this3._getComponentFromScopeIfExist(parsedBitId);
      const files = component.files;

      const foundComponentFromBitMap = _this3.bitMap.getComponentIfExist(component.componentId, {
        ignoreScopeAndVersion: true
      });

      const componentFilesP = files.map(
      /*#__PURE__*/
      function () {
        var _ref3 = (0, _bluebird().coroutine)(function* (file) {
          // $FlowFixMe null is removed later on
          const filePath = _path().default.join(consumerPath, file.relativePath);

          const isAutoGenerated = yield (0, _utils().isAutoGeneratedFile)(filePath);

          if (isAutoGenerated) {
            return null;
          }

          const caseSensitive = false;

          const existingIdOfFile = _this3.bitMap.getComponentIdByPath(file.relativePath, caseSensitive);

          const idOfFileIsDifferent = existingIdOfFile && !existingIdOfFile.isEqual(parsedBitId);
          const existingComponentOfFile = existingIdOfFile ? _this3.bitMap.getComponent(existingIdOfFile) : undefined;

          const isImported = foundComponentFromBitMap && foundComponentFromBitMap.origin === _constants().COMPONENT_ORIGINS.IMPORTED || existingComponentOfFile && existingComponentOfFile.origin === _constants().COMPONENT_ORIGINS.IMPORTED;

          if (isImported) {
            // $FlowFixMe
            const idFromBitMap = foundComponentFromBitMap ? foundComponentFromBitMap.id : existingComponentOfFile.id; // throw error in case user didn't add id to imported component or the id is incorrect

            if (!_this3.id) throw new (_exceptions().MissingComponentIdForImportedComponent)(idFromBitMap.toStringWithoutVersion());

            if (idOfFileIsDifferent) {
              const existingIdWithoutVersion = existingIdOfFile.toStringWithoutVersion(); // $FlowFixMe $this.id is not null at this point

              throw new (_exceptions().IncorrectIdForImportedComponent)(existingIdWithoutVersion, _this3.id, file.relativePath);
            }

            if (_this3._isPackageJsonOnRootDir(file.relativePath, foundComponentFromBitMap)) return null;

            if (_this3._isOutsideOfWrapDir(file.relativePath, foundComponentFromBitMap)) {
              _logger().default.warn(`add-components: ignoring ${file.relativePath} as it is located outside of the wrapDir`);

              return null;
            }

            const isGeneratedForUnsupportedFiles = yield _this3._isGeneratedForUnsupportedFiles(file.relativePath, component.componentId, foundComponentFromBitMap);
            if (isGeneratedForUnsupportedFiles) return null;
            delete component.trackDir;
          } else if (idOfFileIsDifferent) {
            // not imported component file but exists in bitmap
            if (_this3.warnings.alreadyUsed[existingIdOfFile]) {
              _this3.warnings.alreadyUsed[existingIdOfFile].push(file.relativePath);
            } else {
              _this3.warnings.alreadyUsed[existingIdOfFile] = [file.relativePath];
            } // $FlowFixMe null is removed later on


            return null;
          }

          if (!foundComponentFromBitMap && componentFromScope) {
            const newId = componentFromScope.toBitIdWithLatestVersion();
            component.componentId = newId;

            _this3.warnings.existInScope.push(newId);
          }

          return file;
        });

        return function (_x3) {
          return _ref3.apply(this, arguments);
        };
      }()); // $FlowFixMe it can't be null due to the filter function

      const componentFiles = (yield Promise.all(componentFilesP)).filter(file => file);
      if (!componentFiles.length) return {
        id: component.componentId.toString(),
        files: []
      };

      if (foundComponentFromBitMap) {
        const existingRootDir = foundComponentFromBitMap.rootDir;
        if (existingRootDir) _componentMap().default.changeFilesPathAccordingToItsRootDir(existingRootDir, componentFiles);
      }

      if (_this3.trackDirFeature) {
        if (_this3.bitMap._areFilesArraysEqual(foundComponentFromBitMap.files, componentFiles)) {
          return foundComponentFromBitMap;
        }
      }

      if (!_this3.override && foundComponentFromBitMap) {
        _this3._updateFilesWithCurrentLetterCases(foundComponentFromBitMap, componentFiles);

        component.files = _this3._mergeFilesWithExistingComponentMapFiles(componentFiles, foundComponentFromBitMap.files);
      } else {
        component.files = componentFiles;
      }

      const {
        componentId,
        trackDir
      } = component;
      const mainFile = (0, _determineMainFile().default)(component, foundComponentFromBitMap);

      const getComponentMap = () => {
        if (_this3.trackDirFeature) {
          return _this3.bitMap.addFilesToComponent({
            componentId,
            files: component.files
          });
        }

        return _this3.bitMap.addComponent({
          componentId,
          files: component.files,
          mainFile,
          trackDir,
          origin: _constants().COMPONENT_ORIGINS.AUTHORED,
          override: _this3.override
        });
      };

      const componentMap = getComponentMap();
      return {
        id: componentId.toString(),
        files: componentMap.files
      };
    })();
  }
  /**
   * the risk with merging the currently added files with the existing bitMap files is overriding
   * the `test` property. e.g. the component directory is re-added without adding the tests flag to
   * track new files in that directory. in this case, we want to preserve the `test` property.
   */


  _mergeFilesWithExistingComponentMapFiles(componentFiles, existingComponentMapFile) {
    if (this.tests.length) {
      // in case when the `--tests` flag was entered, we use the currently added files as the
      // base and merge the existing file so then the `test` property of the currently added files
      // won't be overridden
      return _ramda().default.unionWith(_ramda().default.eqBy(_ramda().default.prop('relativePath')), componentFiles, existingComponentMapFile);
    }

    return _ramda().default.unionWith(_ramda().default.eqBy(_ramda().default.prop('relativePath')), existingComponentMapFile, componentFiles);
  }
  /**
   * if an existing file is for example uppercase and the new file is lowercase it has different
   * behavior according to the OS. some OS are case sensitive, some are not.
   * it's safer to avoid saving both files and instead, replacing the old file with the new one.
   * in case a file has replaced and it is also a mainFile, replace the mainFile as well
   */


  _updateFilesWithCurrentLetterCases(currentComponentMap, newFiles) {
    const currentFiles = currentComponentMap.files;
    currentFiles.forEach(currentFile => {
      const sameFile = newFiles.find(newFile => newFile.relativePath.toLowerCase() === currentFile.relativePath.toLowerCase());

      if (sameFile && currentFile.relativePath !== sameFile.relativePath) {
        if (currentComponentMap.mainFile === currentFile.relativePath) {
          currentComponentMap.mainFile = sameFile.relativePath;
        }

        currentFile.relativePath = sameFile.relativePath;
      }
    });
  }

  _getComponentFromScopeIfExist(id) {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      try {
        return yield _this4.consumer.scope.getModelComponentIgnoreScope(id);
      } catch (err) {
        return null;
      }
    })();
  } // remove excluded files from file list


  removeExcludedFiles(componentsWithFiles) {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      const files = _ramda().default.flatten(componentsWithFiles.map(x => x.files.map(i => i.relativePath)));

      const resolvedExcludedFiles = yield _this5.getFilesAccordingToDsl(files, _this5.exclude);
      componentsWithFiles.forEach(componentWithFiles => {
        const mainFile = componentWithFiles.mainFile ? (0, _utils().pathNormalizeToLinux)(componentWithFiles.mainFile) : undefined;

        if (resolvedExcludedFiles.includes(mainFile)) {
          componentWithFiles.files = [];
        } else {
          // if mainFile is excluded, exclude all files
          componentWithFiles.files = componentWithFiles.files.filter(key => !resolvedExcludedFiles.includes(key.relativePath));
        }
      });
    })();
  }
  /**
   * if the id is already saved in bitmap file, it might have more data (such as scope, version)
   * use that id instead.
   */


  _getIdAccordingToExistingComponent(currentId) {
    const existingComponentId = this.bitMap.getExistingBitId(currentId, false);
    const componentExists = Boolean(existingComponentId);

    if (componentExists && this.bitMap.getComponent(existingComponentId).origin === _constants().COMPONENT_ORIGINS.NESTED) {
      throw new (_generalError().default)(`One of your dependencies (${existingComponentId}) has already the same namespace and name.
    If you're trying to add a new component, please choose a new namespace or name.
    If you're trying to update a dependency component, please re-import it individually`);
    }

    if (currentId.includes(_constants().VERSION_DELIMITER)) {
      if (!existingComponentId || // this id is new, it shouldn't have a version
      !existingComponentId.hasVersion() || // this component is new, it shouldn't have a version
      // user shouldn't add files to a an existing component with different version
      // $FlowFixMe this function gets called only when this.id is set
      existingComponentId.version !== _bitId().BitId.getVersionOnlyFromString(this.id)) {
        // $FlowFixMe this.id is defined here
        throw new (_versionShouldBeRemoved().default)(this.id);
      }
    }

    return existingComponentId || _bitId().BitId.parse(currentId, false);
  }

  _getIdAccordingToTrackDir(dir) {
    const dirNormalizedToLinux = (0, _utils().pathNormalizeToLinux)(dir);
    const trackDirs = this.bitMap.getAllTrackDirs();
    if (!trackDirs) return null;
    return trackDirs[dirNormalizedToLinux];
  }
  /**
   * used for updating main file if exists or doesn't exists
   */


  _addMainFileToFiles(files) {
    let mainFile = this.main;

    if (mainFile && mainFile.match(REGEX_DSL_PATTERN)) {
      // it's a DSL
      files.forEach(file => {
        const fileInfo = (0, _utils().calculateFileInfo)(file.relativePath);
        const generatedFile = (0, _stringFormat().default)(mainFile, fileInfo);

        const foundFile = this._findMainFileInFiles(generatedFile, files);

        if (foundFile) {
          mainFile = foundFile.relativePath;
        }

        if (_fsExtra().default.existsSync(generatedFile) && !foundFile) {
          const shouldIgnore = this.gitIgnore.ignores(generatedFile);

          if (shouldIgnore) {
            // check if file is in exclude list
            throw new (_exceptions().ExcludedMainFile)(generatedFile);
          }

          files.push({
            relativePath: (0, _utils().pathNormalizeToLinux)(generatedFile),
            test: false,
            name: _path().default.basename(generatedFile)
          });
          mainFile = generatedFile;
        }
      });
    }

    if (!mainFile) return undefined;

    if (this.alternateCwd) {
      mainFile = _path().default.join(this.alternateCwd, mainFile);
    }

    const mainFileRelativeToConsumer = this.consumer.getPathRelativeToConsumer(mainFile);
    const mainPath = this.consumer.toAbsolutePath(mainFileRelativeToConsumer);

    if (_fsExtra().default.existsSync(mainPath)) {
      const shouldIgnore = this.gitIgnore.ignores(mainFileRelativeToConsumer);
      if (shouldIgnore) throw new (_exceptions().ExcludedMainFile)(mainFileRelativeToConsumer);

      if ((0, _utils().isDir)(mainPath)) {
        throw new (_exceptions().MainFileIsDir)(mainPath);
      }

      const foundFile = this._findMainFileInFiles(mainFileRelativeToConsumer, files);

      if (foundFile) {
        return foundFile.relativePath;
      }

      files.push({
        relativePath: (0, _utils().pathNormalizeToLinux)(mainFileRelativeToConsumer),
        test: false,
        name: _path().default.basename(mainFileRelativeToConsumer)
      });
      return mainFileRelativeToConsumer;
    }

    return mainFile;
  }

  _findMainFileInFiles(mainFile, files) {
    const normalizedMainFile = (0, _utils().pathNormalizeToLinux)(mainFile).toLowerCase();
    return files.find(file => file.relativePath.toLowerCase() === normalizedMainFile);
  }

  _mergeTestFilesWithFiles(files) {
    var _this6 = this;

    return (0, _bluebird().coroutine)(function* () {
      const testFiles = !_ramda().default.isEmpty(_this6.tests) ? yield _this6.getFilesAccordingToDsl(files.map(file => file.relativePath), _this6.tests) : [];
      const resolvedTestFiles = testFiles.map(testFile => {
        if ((0, _utils().isDir)(_path().default.join(_this6.consumer.getPath(), testFile))) throw new (_exceptions().TestIsDirectory)(testFile);
        return {
          relativePath: testFile,
          test: true,
          name: _path().default.basename(testFile)
        };
      });
      return (0, _lodash3().default)(resolvedTestFiles, files, 'relativePath');
    })();
  }
  /**
   * given the component paths, prepare the id, mainFile and files to be added later on to bitmap
   * the id of the component is either entered by the user or, if not entered, concluded by the path.
   * e.g. bar/foo.js, the id would be bar/foo.
   * in case bitmap has already the same id, the complete id is taken from bitmap (see _getIdAccordingToExistingComponent)
   */


  addOneComponent(componentPathsStats) {
    var _this7 = this;

    return (0, _bluebird().coroutine)(function* () {
      let finalBitId; // final id to use for bitmap file

      let idFromPath;

      if (_this7.id) {
        finalBitId = _this7._getIdAccordingToExistingComponent(_this7.id);
      }

      const componentsWithFilesP = Object.keys(componentPathsStats).map(
      /*#__PURE__*/
      function () {
        var _ref4 = (0, _bluebird().coroutine)(function* (componentPath) {
          if (componentPathsStats[componentPath].isDir) {
            const relativeComponentPath = _this7.consumer.getPathRelativeToConsumer(componentPath);

            _this7._throwForOutsideConsumer(relativeComponentPath);

            const matches = yield (0, _utils().glob)(_path().default.join(relativeComponentPath, '**'), {
              cwd: _this7.consumer.getPath(),
              nodir: true
            });

            const filteredMatches = _this7.gitIgnore.filter(matches);

            if (!filteredMatches.length) throw new (_exceptions().EmptyDirectory)();
            let filteredMatchedFiles = filteredMatches.map(match => {
              return {
                relativePath: (0, _utils().pathNormalizeToLinux)(match),
                test: false,
                name: _path().default.basename(match)
              };
            }); // merge test files with files

            filteredMatchedFiles = yield _this7._mergeTestFilesWithFiles(filteredMatchedFiles);

            const resolvedMainFile = _this7._addMainFileToFiles(filteredMatchedFiles);

            const absoluteComponentPath = _path().default.resolve(componentPath);

            const splitPath = absoluteComponentPath.split(_path().default.sep);
            const lastDir = splitPath[splitPath.length - 1];

            if (!finalBitId) {
              const idOfTrackDir = _this7._getIdAccordingToTrackDir(componentPath);

              if (idOfTrackDir) {
                finalBitId = idOfTrackDir;
              } else {
                const nameSpaceOrDir = _this7.namespace || splitPath[splitPath.length - 2];

                if (!_this7.namespace) {
                  idFromPath = {
                    namespace: _bitId().BitId.getValidIdChunk(nameSpaceOrDir),
                    name: _bitId().BitId.getValidIdChunk(lastDir)
                  };
                }

                finalBitId = _bitId().BitId.getValidBitId(nameSpaceOrDir, lastDir);
              }
            }

            const trackDir = Object.keys(componentPathsStats).length === 1 && !_this7.exclude.length && _this7.origin === _constants().COMPONENT_ORIGINS.AUTHORED ? relativeComponentPath : undefined;
            return {
              componentId: finalBitId,
              files: filteredMatchedFiles,
              mainFile: resolvedMainFile,
              trackDir,
              idFromPath,
              immediateDir: lastDir
            };
          } // is file


          const absolutePath = _path().default.resolve(componentPath);

          const pathParsed = _path().default.parse(absolutePath);

          const relativeFilePath = _this7.consumer.getPathRelativeToConsumer(componentPath);

          _this7._throwForOutsideConsumer(relativeFilePath);

          if (!finalBitId) {
            let dirName = pathParsed.dir;

            if (!dirName) {
              dirName = _path().default.dirname(absolutePath);
            }

            const nameSpaceOrLastDir = _this7.namespace || _ramda().default.last(dirName.split(_path().default.sep));

            if (!_this7.namespace) {
              idFromPath = {
                namespace: _bitId().BitId.getValidIdChunk(nameSpaceOrLastDir),
                name: _bitId().BitId.getValidIdChunk(pathParsed.name)
              };
            }

            finalBitId = _bitId().BitId.getValidBitId(nameSpaceOrLastDir, pathParsed.name);
          }

          let files = [{
            relativePath: (0, _utils().pathNormalizeToLinux)(relativeFilePath),
            test: false,
            name: _path().default.basename(relativeFilePath)
          }];
          files = yield _this7._mergeTestFilesWithFiles(files);

          const resolvedMainFile = _this7._addMainFileToFiles(files);

          return {
            componentId: finalBitId,
            files,
            mainFile: resolvedMainFile,
            idFromPath
          };
        });

        return function (_x4) {
          return _ref4.apply(this, arguments);
        };
      }());
      let componentsWithFiles = yield Promise.all(componentsWithFilesP); // remove files that are excluded

      if (!_ramda().default.isEmpty(_this7.exclude)) yield _this7.removeExcludedFiles(componentsWithFiles);
      const componentId = finalBitId;
      componentsWithFiles = componentsWithFiles.filter(componentWithFiles => componentWithFiles.files.length); // $FlowFixMe

      if (componentsWithFiles.length === 0) return {
        componentId,
        files: []
      };
      if (componentsWithFiles.length === 1) return componentsWithFiles[0];
      const files = componentsWithFiles.reduce((a, b) => {
        return a.concat(b.files);
      }, []);
      const groupedComponents = (0, _lodash2().default)(files, 'relativePath');
      const uniqComponents = Object.keys(groupedComponents).map(key => (0, _lodash().default)({}, ...groupedComponents[key], (val1, val2) => val1 || val2)); // $FlowFixMe

      return {
        componentId,
        files: uniqComponents,
        mainFile: _ramda().default.head(componentsWithFiles).mainFile,
        trackDir: _ramda().default.head(componentsWithFiles).trackDir,
        idFromPath
      };
    })();
  }

  getIgnoreList() {
    var _this8 = this;

    return (0, _bluebird().coroutine)(function* () {
      const consumerPath = _this8.consumer.getPath();

      let ignoreList = (0, _utils().retrieveIgnoreList)(consumerPath);

      const importedComponents = _this8.bitMap.getAllComponents(_constants().COMPONENT_ORIGINS.IMPORTED);

      const distDirsOfImportedComponents = Object.keys(importedComponents).map(key => (0, _utils().pathJoinLinux)(importedComponents[key].rootDir, _constants().DEFAULT_DIST_DIRNAME, '**'));
      const configsToIgnore = yield _this8.bitMap.getConfigDirsAndFilesToIgnore(_this8.consumer.getPath(), _this8.consumer.config);
      const configDirs = configsToIgnore.dirs.map(dir => (0, _utils().pathJoinLinux)(dir, '**'));
      ignoreList = ignoreList.concat(distDirsOfImportedComponents);
      ignoreList = ignoreList.concat(configsToIgnore.files);
      ignoreList = ignoreList.concat(configDirs);
      return ignoreList;
    })();
  }

  add() {
    var _this9 = this;

    return (0, _bluebird().coroutine)(function* () {
      _this9.ignoreList = yield _this9.getIgnoreList();
      _this9.gitIgnore = (0, _ignore().default)().add(_this9.ignoreList); // add ignore list
      // check unknown test files

      const missingFiles = (0, _utils().getMissingTestFiles)(_this9.tests);

      if (!_ramda().default.isEmpty(missingFiles)) {
        throw new (_exceptions().PathsNotExist)(missingFiles);
      }

      let componentPathsStats = {};

      const resolvedComponentPathsWithoutGitIgnore = _ramda().default.flatten((yield Promise.all(_this9.componentPaths.map(componentPath => (0, _utils().glob)(componentPath))))); // add excluded list to gitignore to remove excluded files from list


      const resolvedExcludedFiles = yield _this9.getFilesAccordingToDsl(resolvedComponentPathsWithoutGitIgnore, _this9.exclude);
      _this9.ignoreList = [..._this9.ignoreList, ...resolvedExcludedFiles];
      _this9.gitIgnore = (0, _ignore().default)().add(_this9.ignoreList); // add ignore list

      const resolvedComponentPathsWithGitIgnore = _this9.gitIgnore.filter(resolvedComponentPathsWithoutGitIgnore); // Run diff on both arrays to see what was filtered out because of the gitignore file


      const diff = (0, _arrayDifference().default)(resolvedComponentPathsWithGitIgnore, resolvedComponentPathsWithoutGitIgnore);

      if (!_ramda().default.isEmpty(_this9.tests) && _this9.id && _ramda().default.isEmpty(resolvedComponentPathsWithoutGitIgnore)) {
        const resolvedTestFiles = _ramda().default.flatten((yield Promise.all(_this9.tests.map(componentPath => (0, _utils().glob)(componentPath)))));

        componentPathsStats = validatePaths(resolvedTestFiles);
      } else {
        if (_ramda().default.isEmpty(resolvedComponentPathsWithoutGitIgnore)) {
          throw new (_exceptions().PathsNotExist)(_this9.componentPaths);
        }

        if (!_ramda().default.isEmpty(resolvedComponentPathsWithGitIgnore)) {
          componentPathsStats = validatePaths(resolvedComponentPathsWithGitIgnore);
        } else {
          throw new (_exceptions().NoFiles)(diff);
        }
      } // if a user entered multiple paths and entered an id, he wants all these paths to be one component
      // conversely, if a user entered multiple paths without id, he wants each dir as an individual component


      const isMultipleComponents = Object.keys(componentPathsStats).length > 1 && !_this9.id;

      if (isMultipleComponents) {
        yield _this9.addMultipleComponents(componentPathsStats);
      } else {
        _logger().default.debugAndAddBreadCrumb('add-components', 'adding one component'); // when a user enters more than one directory, he would like to keep the directories names
        // so then when a component is imported, it will write the files into the original directories


        const addedOne = yield _this9.addOneComponent(componentPathsStats);

        _this9._removeNamespaceIfNotNeeded([addedOne]);

        if (!_ramda().default.isEmpty(addedOne.files)) {
          const addedResult = yield _this9.addOrUpdateComponentInBitMap(addedOne);
          if (addedResult) _this9.addedComponents.push(addedResult);
        }
      }

      _analytics().Analytics.setExtraData('num_components', _this9.addedComponents.length);

      return {
        addedComponents: _this9.addedComponents,
        warnings: _this9.warnings
      };
    })();
  }

  addMultipleComponents(componentPathsStats) {
    var _this10 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debugAndAddBreadCrumb('add-components', 'adding multiple components');

      _this10._removeDirectoriesWhenTheirFilesAreAdded(componentPathsStats);

      const testToRemove = !_ramda().default.isEmpty(_this10.tests) ? yield _this10.getFilesAccordingToDsl(Object.keys(componentPathsStats), _this10.tests) : [];
      testToRemove.forEach(test => delete componentPathsStats[_path().default.normalize(test)]);
      const added = yield _this10._tryAddingMultiple(componentPathsStats);
      validateNoDuplicateIds(added);

      _this10._removeNamespaceIfNotNeeded(added);

      yield _this10._addMultipleToBitMap(added);
    })();
  }
  /**
   * some uses of wildcards might add directories and their files at the same time, in such cases
   * only the files are needed and the directories can be ignored.
   * @see https://github.com/teambit/bit/issues/1406 for more details
   */


  _removeDirectoriesWhenTheirFilesAreAdded(componentPathsStats) {
    const allPaths = Object.keys(componentPathsStats);
    allPaths.forEach(componentPath => {
      const foundDir = allPaths.find(p => p === _path().default.dirname(componentPath));

      if (foundDir && componentPathsStats[foundDir]) {
        _logger().default.debug(`add-components._removeDirectoriesWhenTheirFilesAreAdded, ignoring ${foundDir}`);

        delete componentPathsStats[foundDir];
      }
    });
  }

  _addMultipleToBitMap(added) {
    var _this11 = this;

    return (0, _bluebird().coroutine)(function* () {
      const missingMainFiles = [];
      yield Promise.all(added.map(
      /*#__PURE__*/
      function () {
        var _ref5 = (0, _bluebird().coroutine)(function* (component) {
          if (!_ramda().default.isEmpty(component.files)) {
            try {
              const addedComponent = yield _this11.addOrUpdateComponentInBitMap(component);
              if (addedComponent && addedComponent.files.length) _this11.addedComponents.push(addedComponent);
            } catch (err) {
              if (!(err instanceof _missingMainFile().default)) throw err;
              missingMainFiles.push(err);
            }
          }
        });

        return function (_x5) {
          return _ref5.apply(this, arguments);
        };
      }()));

      if (missingMainFiles.length) {
        throw new (_missingMainFileMultipleComponents().default)(missingMainFiles.map(err => err.componentId).sort());
      }
    })();
  }

  _removeNamespaceIfNotNeeded(addedComponents) {
    const allIds = this.bitMap.getAllBitIds();
    addedComponents.forEach(addedComponent => {
      if (!addedComponent.idFromPath) return; // when the id was not generated from the path do nothing.

      const componentsWithSameName = addedComponents // $FlowFixMe
      .filter(a => a.idFromPath && a.idFromPath.name === addedComponent.idFromPath.name); // $FlowFixMe

      const bitIdFromNameOnly = new (_bitId().BitId)({
        name: addedComponent.idFromPath.name
      });
      const existingComponentWithSameName = allIds.searchWithoutScopeAndVersion(bitIdFromNameOnly);

      if (componentsWithSameName.length === 1 && !existingComponentWithSameName) {
        addedComponent.componentId = bitIdFromNameOnly;
      }
    });
  }

  _tryAddingMultiple(componentPathsStats) {
    var _this12 = this;

    return (0, _bluebird().coroutine)(function* () {
      const addedP = Object.keys(componentPathsStats).map(
      /*#__PURE__*/
      function () {
        var _ref6 = (0, _bluebird().coroutine)(function* (onePath) {
          const oneComponentPathStat = {
            [onePath]: componentPathsStats[onePath]
          };

          try {
            const addedComponent = yield _this12.addOneComponent(oneComponentPathStat);
            return addedComponent;
          } catch (err) {
            if (!(err instanceof _exceptions().EmptyDirectory)) throw err;

            _this12.warnings.emptyDirectory.push(onePath);

            return null;
          }
        });

        return function (_x6) {
          return _ref6.apply(this, arguments);
        };
      }());
      const added = yield Promise.all(addedP);
      return _ramda().default.reject(_ramda().default.isNil, added);
    })();
  }

  _throwForOutsideConsumer(relativeToConsumerPath) {
    if (relativeToConsumerPath.startsWith('..')) {
      throw new (_pathOutsideConsumer().default)(relativeToConsumerPath);
    }
  }

}
/**
 * validatePaths - validate if paths entered by user exist and if not throw an error
 *
 * @param {string[]} fileArray - array of paths
 * @returns {PathsStats} componentPathsStats
 */


exports.default = AddComponents;

function validatePaths(fileArray) {
  const componentPathsStats = {};
  fileArray.forEach(componentPath => {
    if (!_fsExtra().default.existsSync(componentPath)) {
      throw new (_exceptions().PathsNotExist)([componentPath]);
    }

    componentPathsStats[componentPath] = {
      isDir: (0, _utils().isDir)(componentPath)
    };
  });
  return componentPathsStats;
}
/**
 * validate that no two files where added with the same id in the same bit add command
 */


function validateNoDuplicateIds(addComponents) {
  const duplicateIds = {};
  const newGroupedComponents = (0, _lodash2().default)(addComponents, 'componentId');
  Object.keys(newGroupedComponents).forEach(key => {
    if (newGroupedComponents[key].length > 1) duplicateIds[key] = newGroupedComponents[key];
  });
  if (!_ramda().default.isEmpty(duplicateIds) && !_ramda().default.isNil(duplicateIds)) throw new (_exceptions().DuplicateIds)(duplicateIds);
}