"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 _defineProperty2() {
  const data = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));

  _defineProperty2 = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _dist() {
  const data = _interopRequireDefault(require("./dist"));

  _dist = 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 _links() {
  const data = require("../../../links");

  _links = function () {
    return data;
  };

  return data;
}

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

  _utils = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

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

  _path2 = function () {
    return data;
  };

  return data;
}

function _source() {
  const data = _interopRequireDefault(require("../../../scope/models/source"));

  _source = function () {
    return data;
  };

  return data;
}

function _dataToPersist() {
  const data = _interopRequireDefault(require("./data-to-persist"));

  _dataToPersist = function () {
    return data;
  };

  return data;
}

function _workspaceConfig() {
  const data = _interopRequireDefault(require("../../config/workspace-config"));

  _workspaceConfig = function () {
    return data;
  };

  return data;
}

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

  _scope = function () {
    return data;
  };

  return data;
}

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

  _bitMap = function () {
    return data;
  };

  return data;
}

function _manipulateDir() {
  const data = require("../../component-ops/manipulate-dir");

  _manipulateDir = function () {
    return data;
  };

  return data;
}

/**
 * Dist paths are by default saved into the component's root-dir/dist. However, when dist is set in bit.json, the paths
 * are in the consumer-root/dist.target dir. If dist.entry is set, the dist.entry part is stripped from the dists paths.
 * (according to some additional conditions. See shouldDistEntryBeStripped()).
 * If there is originallySharedDir and the component is IMPORTED, it is stripped as well.
 *
 * These modifications of the paths are taken care in different stages depends on the scenario.
 * 1) using 'bit build'.
 * First, the sharedOriginallyDir is stripped (happens in consumer-component.build()). There are two scenarios here:
 *   a) the component wasn't change since the last build. It loads the dists from the model and strip the
 *      sharedOriginallyDir. (see the !needToRebuild case of build()).
 *   b) the component was changed. It re-builds it. The dists path are cloned from the files, since the files are
 *      sharedOriginallyDir stripped (because they loaded from the filesystem), so will be the dists files.
 * Next, the dist.entry is stripped. This is done when the dists are written into the file-system,  (see writeDists()).
 *
 * 2) using 'bit import'.
 * When converting the component from model to consumer-component, the sharedOriginallyDir is stripped. (see
 * stripOriginallySharedDir() ).
 * Then, Before writing the dists to the file-system, the dist-entry is taken care of. (see writeDists() ).
 *
 * 3) using 'bit link'.
 * When linking authored components, we generate an index file from node_modules/component-name to the main dist file.
 * It might happen during the import, when updateDistsPerWorkspaceConfig() was running already, and it might happen
 * during the 'bit link' command. Therefore, before linking, the updateDistsPerWorkspaceConfig() is running while making
 * sure it doesn't run twice.
 * (see node-modules-linker.linkToMainFile() and calculateMainDistFileForAuthored()).
 *
 * The opposite action is taken when a component is tagged. We load the component from the file-system while the dist
 * paths might be stripped from consumer dist.entry and originallySharedDir.
 * Then, before writing them to the model, we first add the originallySharedDir and then the dist.entry. We make sure
 * there were stripped before adding them. (See this.toDistFilesModel() function and the comment there)
 */
class Dists {
  // changed only when importing a component
  // populated only after getDistDirForConsumer() is called
  constructor(dists, mainDistFile) {
    (0, _defineProperty2().default)(this, "writeDistsFiles", true);
    (0, _defineProperty2().default)(this, "areDistsInsideComponentDir", true);
    (0, _defineProperty2().default)(this, "distEntryShouldBeStripped", false);
    this._mainDistFile = mainDistFile;
    this.dists = dists || []; // cover also case of null (when it comes from the model)
  }

  isEmpty() {
    return !this.dists.length;
  }

  get() {
    return this.dists;
  }

  getAsReadable() {
    return this.dists.map(file => file.toReadableString());
  }

  getMainDistFile() {
    return this._mainDistFile;
  }
  /**
   * When dists are written by a consumer (as opposed to isolated-environment for example), the dist-entry and
   * dist-target are taken into account for calculating the path.
   * By default, the dists path is inside the component. If dist attribute is populated in bit.json, the path is
   * relative to consumer root.
   */


  getDistDirForConsumer(consumer, componentRootDir) {
    if (consumer.shouldDistsBeInsideTheComponent()) {
      this.distsRootDir = _path().default.join(componentRootDir, _constants().DEFAULT_DIST_DIRNAME);
    } else {
      this.areDistsInsideComponentDir = false;
      this.distsRootDir = Dists.getDistDirWhenDistIsOutsideCompDir(consumer.config, componentRootDir);
    }

    return this.distsRootDir;
  }

  static getDistDirWhenDistIsOutsideCompDir(workspaceConfig, componentRootDir) {
    if (workspaceConfig.distEntry) componentRootDir = componentRootDir.replace(workspaceConfig.distEntry, '');

    const distTarget = workspaceConfig.distTarget || _constants().DEFAULT_DIST_DIRNAME;

    return _path().default.join(distTarget, componentRootDir);
  }

  getDistDir(consumer, componentRootDir) {
    if (consumer) return this.getDistDirForConsumer(consumer, componentRootDir);
    this.distsRootDir = _path().default.join(componentRootDir, _constants().DEFAULT_DIST_DIRNAME);
    return this.distsRootDir;
  }

  updateDistsPerWorkspaceConfig(id, consumer, componentMap) {
    if (this.isEmpty()) return;
    const newDistBase = this.getDistDir(consumer, componentMap.getRootDir());
    this.dists.forEach(dist => dist.updatePaths({
      newBase: newDistBase
    }));
    if (consumer) this.stripDistEntryIfNeeded(id, consumer, componentMap);
  }

  stripDistEntryIfNeeded(id, consumer, componentMap) {
    const distEntry = consumer.config.distEntry;
    if (!distEntry) return;

    const shouldDistEntryBeStripped = () => {
      if (this.distEntryShouldBeStripped) return false; // it has been already stripped, don't strip twice!

      if (!distEntry || componentMap.origin === _constants().COMPONENT_ORIGINS.NESTED) return false;

      const areAllDistsStartWithDistEntry = () => {
        return this.dists.map(dist => dist.relative.startsWith(distEntry)).every(x => x);
      };

      if (componentMap.origin === _constants().COMPONENT_ORIGINS.AUTHORED) {
        return areAllDistsStartWithDistEntry();
      } // it's IMPORTED. We first check that rootDir starts with dist.entry, it happens mostly when a user imports into
      // a specific directory (e.g. bit import --path src/). Then, we make sure all dists files start with that
      // dist.entry. In a case when originallySharedDir is the same as dist.entry, this second check returns false.
      // $FlowFixMe


      return componentMap.rootDir.startsWith(distEntry) && areAllDistsStartWithDistEntry();
    };

    const distEntryShouldBeStripped = shouldDistEntryBeStripped();
    if (!distEntryShouldBeStripped) return;

    _logger().default.debug(`stripping dist.entry "${distEntry}" from ${id.toString()}`);

    this.distEntryShouldBeStripped = true;
    this.dists.forEach(dist => dist.updatePaths({
      newRelative: dist.relative.replace(distEntry, '')
    }));

    if (this._mainDistFile) {
      this._mainDistFile.replace(distEntry, '');
    }
  }

  stripOriginallySharedDir(originallySharedDir) {
    this.dists.forEach(distFile => {
      const newRelative = (0, _manipulateDir().stripSharedDirFromPath)(distFile.relative, originallySharedDir);
      distFile.updatePaths({
        newRelative
      });
    });
    this._mainDistFile = this._mainDistFile ? (0, _manipulateDir().stripSharedDirFromPath)(this._mainDistFile, originallySharedDir) : this._mainDistFile;
  }
  /**
   * write dists files to the filesystem
   */


  writeDists(component, consumer, writeLinks = true) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const dataToPersist = yield _this.getDistsToWrite(component, consumer.bitMap, consumer, writeLinks);
      if (!dataToPersist) return null;
      if (consumer) dataToPersist.addBasePath(consumer.getPath());
      yield dataToPersist.persistAllToFS();
      return _this.dists.map(distFile => distFile.path);
    })();
  }
  /**
   * write dist link files to the filesystem
   */


  writeDistsLinks(component, consumer) {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this2.isEmpty() || !_this2.writeDistsFiles) return;
      const componentMap = consumer ? consumer.bitMap.getComponent(component.id, {
        ignoreVersion: true
      }) : component.componentMap;
      if (!componentMap) throw new Error('writeDistsLinks expect componentMap to be defined');
      if (componentMap.origin !== _constants().COMPONENT_ORIGINS.IMPORTED) return;
      const dataToPersist = yield (0, _links().getLinksInDistToWrite)(component, componentMap, consumer, consumer.bitMap);
      dataToPersist.addBasePath(consumer.getPath());
      yield dataToPersist.persistAllToFS();
    })();
  }
  /**
   * In case there is a consumer and dist.entry should be stripped, it will be done before writing the files.
   * The originallySharedDir should be already stripped before accessing this method.
   */


  getDistsToWrite(component, bitMap, consumer, writeLinks = true, componentWithDependencies) {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this3.isEmpty() || !_this3.writeDistsFiles) return null;
      const dataToPersist = new (_dataToPersist().default)();
      const componentMap = consumer ? consumer.bitMap.getComponent(component.id, {
        ignoreVersion: true
      }) : component.componentMap;
      if (!componentMap) throw new Error('getDistsToWrite expect componentMap to be defined');

      _this3.updateDistsPerWorkspaceConfig(component.id, consumer, componentMap);

      dataToPersist.addManyFiles(_this3.dists);

      if (writeLinks && componentMap && componentMap.origin === _constants().COMPONENT_ORIGINS.IMPORTED) {
        const linksInDist = yield (0, _links().getLinksInDistToWrite)(component, componentMap, consumer, bitMap, componentWithDependencies);
        dataToPersist.merge(linksInDist);
      }

      return dataToPersist;
    })();
  } // In case there are dist files, we want to point the index to the main dist file, not to source.
  // This important since when you require a module without specify file, it will give you the file specified under this key
  // (or index.js if key not exists)


  calculateMainDistFile(mainSourceFile) {
    if (this.writeDistsFiles && this.areDistsInsideComponentDir) {
      const getMainFile = () => {
        if (this._mainDistFile) return this._mainDistFile; // Take the only dist file if there is only one or search for one with the same name as the main source file

        if (this.dists && this.dists.length === 1) return this.dists[0].relative;
        return (0, _utils().searchFilesIgnoreExt)(this.dists, mainSourceFile, 'relative');
      };

      const mainFile = getMainFile();
      if (mainFile) return _path().default.join(_constants().DEFAULT_DIST_DIRNAME, mainFile);
    }

    return this._mainDistFile || mainSourceFile;
  }
  /**
   * authored components have the dists outside the components dir and they don't have rootDir.
   * it returns the file or dist file relative to consumer-root.
   */


  calculateDistFileForAuthored(componentFile, consumer, isMain) {
    if (this.isEmpty()) return componentFile;

    const getFileToSearch = () => {
      if (!consumer.config.distEntry) return componentFile;

      const distEntryNormalized = _path().default.normalize(consumer.config.distEntry);

      return componentFile.replace(`${distEntryNormalized}${_path().default.sep}`, '');
    };

    const fileToSearch = getFileToSearch();
    const distFile = isMain && this._mainDistFile ? this._mainDistFile : (0, _utils().searchFilesIgnoreExt)(this.dists, fileToSearch, 'relative');
    if (!distFile) return componentFile;

    const distTarget = consumer.config.distTarget || _constants().DEFAULT_DIST_DIRNAME;

    return _path().default.join(distTarget, distFile);
  }

  toDistFilesModel(consumer, originallySharedDir, compiler) {
    // when a component is written to the filesystem, the originallySharedDir may be stripped, if it was, the
    // originallySharedDir is written in bit.map, and then set in consumerComponent.originallySharedDir when loaded.
    // similarly, when the dists are written to the filesystem, the dist.entry may be stripped, if it was, the
    // consumerComponent.dists.distEntryShouldBeStripped is set to true.
    // because the model always has the paths of the original author, in case part of the path was stripped, add it
    // back before saving to the model. this way, when the author updates the components, the paths will be correct.
    const addSharedDirAndDistEntry = pathStr => {
      const withSharedDir = originallySharedDir ? _path().default.join(originallySharedDir, pathStr) : pathStr;
      const withDistEntry = this.distEntryShouldBeStripped // $FlowFixMe
      ? _path().default.join(consumer.config.distEntry, withSharedDir) : withSharedDir;
      return (0, _path2().pathNormalizeToLinux)(withDistEntry);
    };

    if (this.isEmpty() || !compiler) return {};
    const dists = this.get().map(dist => {
      return {
        name: dist.basename,
        relativePath: addSharedDirAndDistEntry(dist.relative),
        file: _source().default.from(dist.contents),
        test: dist.test
      };
    });
    const mainDistFile = this._mainDistFile ? addSharedDirAndDistEntry(this._mainDistFile) : this._mainDistFile; // $FlowFixMe

    return {
      dists,
      mainDistFile
    };
  }
  /**
   * the formula is distTarget + (customModuleDir - distEntry).
   * e.g. distTarget = 'dist', customDir = 'src', distEntry = 'src'.
   * node_path will be 'dist' + 'src' - 'src' = 'dist'.
   * another example, distTarget = 'dist', customDir = 'src/custom', distEntry = 'src'. result: "dist/custom"
   */


  static getNodePathDir(consumer) {
    const resolveModules = consumer.config.resolveModules;
    if (!resolveModules || !resolveModules.modulesDirectories || !resolveModules.modulesDirectories.length) return null;

    const distTarget = consumer.config.distTarget || _constants().DEFAULT_DIST_DIRNAME;

    const distEntry = consumer.config.distEntry;
    const nodePaths = resolveModules.modulesDirectories.map(moduleDir => {
      const isRelative = str => str.startsWith('./') || str.startsWith('../');

      if (!distEntry) return _path().default.join(distTarget, moduleDir);
      const distEntryRelativeToModuleDir = (0, _utils().pathRelativeLinux)(distEntry, moduleDir);

      if (isRelative(distEntryRelativeToModuleDir)) {
        // moduleDir is outside the distEntry, ignore the distEntry
        return _path().default.join(distTarget, moduleDir);
      }

      return _path().default.join(distTarget, distEntryRelativeToModuleDir);
    });
    return nodePaths.map(nodePath => consumer.toAbsolutePath(nodePath)).map(_path2().pathNormalizeToLinux).join(_constants().NODE_PATH_SEPARATOR);
  }

  clone() {
    // $FlowFixMe
    const clone = Object.assign(Object.create(Object.getPrototypeOf(this)), this);
    clone.dists = this.dists.map(d => d.clone());
    return clone;
  }

}

exports.default = Dists;