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

  _ramda = function () {
    return data;
  };

  return data;
}

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

  _path = function () {
    return data;
  };

  return data;
}

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

  _semver = function () {
    return data;
  };

  return data;
}

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

  _pMapSeries = function () {
    return data;
  };

  return data;
}

function _capsule() {
  const data = _interopRequireDefault(require("../../components/core/capsule"));

  _capsule = function () {
    return data;
  };

  return data;
}

function _capsuleFactory() {
  const data = _interopRequireDefault(require("./capsule-factory"));

  _capsuleFactory = function () {
    return data;
  };

  return data;
}

function _consumer() {
  const data = _interopRequireDefault(require("../consumer/consumer"));

  _consumer = function () {
    return data;
  };

  return data;
}

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

  _scope = function () {
    return data;
  };

  return data;
}

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

  _bitId = function () {
    return data;
  };

  return data;
}

function _manyComponentsWriter() {
  const data = _interopRequireDefault(require("../consumer/component-ops/many-components-writer"));

  _manyComponentsWriter = function () {
    return data;
  };

  return data;
}

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

  _logger = function () {
    return data;
  };

  return data;
}

function _loadFlattenedDependencies() {
  const data = _interopRequireDefault(require("../consumer/component-ops/load-flattened-dependencies"));

  _loadFlattenedDependencies = function () {
    return data;
  };

  return data;
}

function _packageJsonFile() {
  const data = _interopRequireDefault(require("../consumer/component/package-json-file"));

  _packageJsonFile = function () {
    return data;
  };

  return data;
}

function _packageJsonUtils() {
  const data = require("../consumer/component/package-json-utils");

  _packageJsonUtils = function () {
    return data;
  };

  return data;
}

function _componentIdToPackageName() {
  const data = _interopRequireDefault(require("../utils/bit/component-id-to-package-name"));

  _componentIdToPackageName = function () {
    return data;
  };

  return data;
}

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

  _constants = function () {
    return data;
  };

  return data;
}

function _npmClient() {
  const data = _interopRequireDefault(require("../npm-client"));

  _npmClient = function () {
    return data;
  };

  return data;
}

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

  _componentsGraph = function () {
    return data;
  };

  return data;
}

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

  _dataToPersist = function () {
    return data;
  };

  return data;
}

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

  _bitMap = function () {
    return data;
  };

  return data;
}

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

  _manipulateDir = function () {
    return data;
  };

  return data;
}

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

  _generalError = function () {
    return data;
  };

  return data;
}

class Isolator {
  // this is the same packageJson of the main component as it located on the root
  constructor(capsule, scope, consumer) {
    (0, _defineProperty2().default)(this, "_npmVersionHasValidated", false);
    this.capsule = capsule;
    this.scope = scope;
    this.consumer = consumer;
  }

  static getInstance(containerType = 'fs', scope, consumer, dir) {
    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug(`Isolator.getInstance, creating a capsule with an ${containerType} container, dir ${dir || 'N/A'}`);

      const capsule = yield (0, _capsuleFactory().default)(containerType, dir);
      return new Isolator(capsule, scope, consumer);
    })();
  }

  isolate(componentId, opts) {
    var _this = this;

    return (0, _bluebird().coroutine)(function* () {
      const componentWithDependencies = yield _this._loadComponent(componentId);

      if (opts.shouldBuildDependencies) {
        (0, _componentsGraph().topologicalSortComponentDependencies)(componentWithDependencies);
        yield (0, _pMapSeries().default)(componentWithDependencies.dependencies.reverse(),
        /*#__PURE__*/
        function () {
          var _ref = (0, _bluebird().coroutine)(function* (dep) {
            if (!dep.dists || dep.dists.isEmpty()) {
              yield dep.build({
                scope: _this.scope,
                consumer: _this.consumer
              });
              dep.dists.stripOriginallySharedDir(dep.originallySharedDir);
            }
          });

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

      const writeToPath = opts.writeToPath;
      const concreteOpts = {
        componentsWithDependencies: [componentWithDependencies],
        writeToPath,
        override: opts.override,
        writePackageJson: !opts.noPackageJson,
        writeConfig: opts.conf,
        writeBitDependencies: opts.writeBitDependencies,
        createNpmLinkFiles: opts.createNpmLinkFiles,
        saveDependenciesAsComponents: opts.saveDependenciesAsComponents !== false,
        writeDists: opts.dist,
        installNpmPackages: !!opts.installPackages,
        // convert to boolean
        installPeerDependencies: !!opts.installPackages,
        // convert to boolean
        addToRootPackageJson: false,
        verbose: opts.verbose,
        excludeRegistryPrefix: !!opts.excludeRegistryPrefix,
        silentPackageManagerResult: opts.silentPackageManagerResult,
        isolated: true,
        capsule: _this.capsule
      };
      _this.componentWithDependencies = componentWithDependencies;
      _this.manyComponentsWriter = new (_manyComponentsWriter().default)(concreteOpts);
      yield _this.writeComponentsAndDependencies();
      yield _this.installComponentPackages();
      yield _this.writeLinks();
      _this.capsuleBitMap = _this.manyComponentsWriter.bitMap;
      return componentWithDependencies;
    })();
  }

  writeComponentsAndDependencies() {
    var _this2 = this;

    return (0, _bluebird().coroutine)(function* () {
      _logger().default.debug('ManyComponentsWriter, writeAllToIsolatedCapsule');

      _this2._manipulateDir();

      yield _this2.manyComponentsWriter._populateComponentsFilesToWrite();
      yield _this2.manyComponentsWriter._populateComponentsDependenciesToWrite();
      yield _this2._persistComponentsDataToCapsule();
    })();
  }

  installComponentPackages() {
    var _this3 = this;

    return (0, _bluebird().coroutine)(function* () {
      // $FlowFixMe
      _this3.capsulePackageJson = _this3.componentWithDependencies.component.packageJsonFile; // $FlowFixMe

      _this3.componentRootDir = _this3.componentWithDependencies.component.writtenPath;
      yield _this3._addComponentsToRoot();

      _logger().default.debug('ManyComponentsWriter, install packages on capsule');

      yield _this3._installWithPeerOption();
    })();
  }

  writeLinks() {
    var _this4 = this;

    return (0, _bluebird().coroutine)(function* () {
      const links = yield _this4.manyComponentsWriter._getAllLinks();
      yield links.persistAllToCapsule(_this4.capsule);
    })();
  }
  /**
   * used by compilers that create capsule.
   * when installing packages on the capsule, the links generated on node_modules may be deleted
   */


  writeLinksOnNodeModules() {
    var _this5 = this;

    return (0, _bluebird().coroutine)(function* () {
      const links = yield _this5.manyComponentsWriter._getAllLinks();
      const nodeModulesLinks = links.filterByPath(filePath => filePath.startsWith('node_modules'));
      yield nodeModulesLinks.persistAllToCapsule(_this5.capsule);
    })();
  }

  _manipulateDir() {
    const allComponents = [this.componentWithDependencies.component, ...this.componentWithDependencies.allDependencies];
    const manipulateDirData = (0, _manipulateDir().getManipulateDirForComponentWithDependencies)(this.componentWithDependencies);
    allComponents.forEach(component => {
      component.stripOriginallySharedDir(manipulateDirData);
    });
  }
  /**
   * To write a component into an isolated environment, we need not only its dependencies, but
   * also the dependencies of its dependencies and so on.
   * When loading a component from the model, it's easy to get them all from the
   * flattenedDependencies. However, when loading from the consumer, we have only the dependencies
   * loaded, not the flattened. To get the flattened, we have to load the dependencies and each one
   * of the dependency we need to load its dependencies as well until we got them all.
   * Also, we have to clone each component we load, because when writing them into the capsule, we
   * strip their shared-dir and we don't want these changed paths to affect the workspace
   */


  _loadComponent(id) {
    var _this6 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this6.consumer) {
        return _this6._loadComponentFromConsumer(id);
      }

      throw new Error('loading components from scope is not implemented yet');
    })();
  }

  _loadComponentFromConsumer(id) {
    var _this7 = this;

    return (0, _bluebird().coroutine)(function* () {
      const consumer = _this7.consumer;
      if (!consumer) throw new Error('missing consumer');
      const component = yield consumer.loadComponentForCapsule(id);
      const forCapsule = true;
      return (0, _loadFlattenedDependencies().default)(consumer, component, forCapsule);
    })();
  }

  _persistComponentsDataToCapsule() {
    var _this8 = this;

    return (0, _bluebird().coroutine)(function* () {
      const dataToPersist = new (_dataToPersist().default)();
      const allComponents = [_this8.componentWithDependencies.component, ..._this8.componentWithDependencies.allDependencies];
      allComponents.forEach(component => dataToPersist.merge(component.dataToPersist));
      yield dataToPersist.persistAllToCapsule(_this8.capsule);
    })();
  }

  _addComponentsToRoot() {
    var _this9 = this;

    return (0, _bluebird().coroutine)(function* () {
      const capsulePath = _this9.capsule.container.getPath(); // the capsulePath hack only works for the fs-capsule
      // for other capsule types, we would need to do this
      // (and other things) inside the capsule itself
      // rather than fetching its folder and using it


      const rootPathInCapsule = _path().default.join(capsulePath, _this9.componentRootDir);

      const componentsToAdd = _this9.componentWithDependencies.allDependencies.reduce((acc, component) => {
        // $FlowFixMe - writtenPath is defined
        const componentPathInCapsule = _path().default.join(capsulePath, component.writtenPath);

        const relativeDepLocation = _path().default.relative(rootPathInCapsule, componentPathInCapsule);

        const locationAsUnixFormat = (0, _packageJsonUtils().convertToValidPathForPackageManager)(relativeDepLocation);
        const packageName = (0, _componentIdToPackageName().default)(component.id, component.bindingPrefix);
        acc[packageName] = locationAsUnixFormat;
        return acc;
      }, {});

      if (_ramda().default.isEmpty(componentsToAdd)) return;

      _this9.capsulePackageJson.addDependencies(componentsToAdd);

      yield _this9._writeCapsulePackageJson();
    })();
  }

  _writeCapsulePackageJson() {
    var _this10 = this;

    return (0, _bluebird().coroutine)(function* () {
      const dataToPersist = new (_dataToPersist().default)();
      dataToPersist.addFile(_this10.capsulePackageJson.toVinylFile());
      dataToPersist.persistAllToCapsule(_this10.capsule);
    })();
  }

  _getNpmVersion() {
    var _this11 = this;

    return (0, _bluebird().coroutine)(function* () {
      const versionString = yield _this11.capsuleExec('npm --version');

      const validVersion = _semver().default.coerce(versionString);

      return validVersion ? validVersion.raw : null;
    })();
  }

  installPackagesOnRoot(modules = []) {
    var _this12 = this;

    return (0, _bluebird().coroutine)(function* () {
      yield _this12._throwForOldNpmVersion();
      const args = ['install', ...modules, '--no-save'];
      return _this12.capsuleExec(`npm ${args.join(' ')}`, {
        cwd: _this12.componentRootDir
      });
    })();
  }

  _throwForOldNpmVersion() {
    var _this13 = this;

    return (0, _bluebird().coroutine)(function* () {
      if (_this13._npmVersionHasValidated) {
        return;
      }

      const npmVersion = yield _this13._getNpmVersion();

      if (!npmVersion) {
        throw new Error('Failed to isolate component: unable to run npm');
      }

      if (!_semver().default.satisfies(npmVersion, _constants().ACCEPTABLE_NPM_VERSIONS)) {
        throw new (_generalError().default)(`Failed to isolate component: found an old version of npm (${npmVersion}). ` + `To get rid of this error, please upgrade to npm ${_constants().ACCEPTABLE_NPM_VERSIONS}`);
      }

      _this13._npmVersionHasValidated = true;
    })();
  }

  capsuleExec(cmd, options) {
    var _this14 = this;

    return (0, _bluebird().coroutine)(function* () {
      const execResults = yield _this14.capsule.exec(cmd, options);
      let output = '';
      return new Promise((resolve, reject) => {
        execResults.stdout.on('data', data => {
          output += data;
        });
        execResults.stdout.on('error', error => {
          return reject(error);
        }); // @ts-ignore

        execResults.on('close', () => {
          return resolve(output);
        });
      });
    })();
  }
  /**
   * it must be done in this order. first, `npm install`, then, `npm list -j` shows the missing
   * peer dependencies, then, add these peerDependencies into devDependencies and run `npm install`
   * again. The reason for adding the missing peer into devDependencies is to not get them deleted
   * once `npm install` is running along the road.
   */


  _installWithPeerOption(installPeerDependencies = true) {
    var _this15 = this;

    return (0, _bluebird().coroutine)(function* () {
      yield _this15.installPackagesOnRoot();

      if (installPeerDependencies) {
        const peers = yield _this15._getPeerDependencies();

        if (!_ramda().default.isEmpty(peers)) {
          _this15.capsulePackageJson.packageJsonObject.devDependencies = Object.assign(_this15.capsulePackageJson.packageJsonObject.devDependencies || {}, peers);
          yield _this15._writeCapsulePackageJson();
          yield _this15.installPackagesOnRoot();
        }
      }
    })();
  }

  _getPeerDependencies() {
    var _this16 = this;

    return (0, _bluebird().coroutine)(function* () {
      const packageManager = _constants().DEFAULT_PACKAGE_MANAGER;

      let npmList;

      try {
        npmList = yield _this16._getNpmListOutput(packageManager);
      } catch (err) {
        _logger().default.error(err);

        throw new Error(`failed running "${packageManager} list -j" to find the peer dependencies due to an error: ${err}`);
      }

      return _npmClient().default.getPeerDepsFromNpmList(npmList, packageManager);
    })();
  }

  _getNpmListOutput(packageManager) {
    var _this17 = this;

    return (0, _bluebird().coroutine)(function* () {
      const args = [packageManager, 'list', '-j'];

      try {
        return yield _this17.capsuleExec(args.join(' '), {
          cwd: _this17.componentRootDir
        });
      } catch (err) {
        if (err && err.startsWith('{')) return err; // it's probably a valid json with errors, that's fine, parse it.

        throw err;
      }
    })();
  }

}

exports.default = Isolator;