"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.UiMain = void 0;
function _fs() {
  const data = require("fs");
  _fs = function () {
    return data;
  };
  return data;
}
function _aspectLoader() {
  const data = require("@teambit/aspect-loader");
  _aspectLoader = function () {
    return data;
  };
  return data;
}
function _cache() {
  const data = require("@teambit/cache");
  _cache = function () {
    return data;
  };
  return data;
}
function _cli() {
  const data = require("@teambit/cli");
  _cli = function () {
    return data;
  };
  return data;
}
function _component() {
  const data = require("@teambit/component");
  _component = function () {
    return data;
  };
  return data;
}
function _express() {
  const data = require("@teambit/express");
  _express = function () {
    return data;
  };
  return data;
}
function _graphql() {
  const data = require("@teambit/graphql");
  _graphql = function () {
    return data;
  };
  return data;
}
function _chalk() {
  const data = _interopRequireDefault(require("chalk"));
  _chalk = function () {
    return data;
  };
  return data;
}
function _harmony() {
  const data = require("@teambit/harmony");
  _harmony = function () {
    return data;
  };
  return data;
}
function _logger() {
  const data = require("@teambit/logger");
  _logger = function () {
    return data;
  };
  return data;
}
function _pubsub() {
  const data = require("@teambit/pubsub");
  _pubsub = function () {
    return data;
  };
  return data;
}
function _utils() {
  const data = require("@teambit/legacy/dist/utils");
  _utils = function () {
    return data;
  };
  return data;
}
function _pMapSeries() {
  const data = _interopRequireDefault(require("p-map-series"));
  _pMapSeries = function () {
    return data;
  };
  return data;
}
function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));
  _fsExtra = function () {
    return data;
  };
  return data;
}
function _toolboxNetwork() {
  const data = require("@teambit/toolbox.network.get-port");
  _toolboxNetwork = function () {
    return data;
  };
  return data;
}
function _harmonyModules() {
  const data = require("@teambit/harmony.modules.harmony-root-generator");
  _harmonyModules = function () {
    return data;
  };
  return data;
}
function _path() {
  const data = require("path");
  _path = function () {
    return data;
  };
  return data;
}
function _util() {
  const data = require("util");
  _util = function () {
    return data;
  };
  return data;
}
function _webpack() {
  const data = _interopRequireDefault(require("webpack"));
  _webpack = function () {
    return data;
  };
  return data;
}
function _events() {
  const data = require("./events");
  _events = function () {
    return data;
  };
  return data;
}
function _exceptions() {
  const data = require("./exceptions");
  _exceptions = function () {
    return data;
  };
  return data;
}
function _start() {
  const data = require("./start.cmd");
  _start = function () {
    return data;
  };
  return data;
}
function _uiBuild() {
  const data = require("./ui-build.cmd");
  _uiBuild = function () {
    return data;
  };
  return data;
}
function _uiServer() {
  const data = require("./ui-server");
  _uiServer = function () {
    return data;
  };
  return data;
}
function _ui() {
  const data = require("./ui.aspect");
  _ui = function () {
    return data;
  };
  return data;
}
function _webpackBrowser() {
  const data = _interopRequireDefault(require("./webpack/webpack.browser.config"));
  _webpackBrowser = function () {
    return data;
  };
  return data;
}
function _webpackSsr() {
  const data = _interopRequireDefault(require("./webpack/webpack.ssr.config"));
  _webpackSsr = function () {
    return data;
  };
  return data;
}
function _bundleUi() {
  const data = require("./bundle-ui.task");
  _bundleUi = function () {
    return data;
  };
  return data;
}
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
class UiMain {
  constructor(
  /**
   * Pubsub extension.
   */
  pubsub, config,
  /**
   * graphql extension.
   */
  graphql,
  /**
   * slot registry of ui roots.
   */
  uiRootSlot,
  /**
   * express extension.
   */
  express,
  /**
   * pre-start slot
   */
  preStartSlot,
  /**
   * on start slot
   */
  onStartSlot,
  /**
   * Overwrite the public dir Slot
   */
  publicDirOverwriteSlot,
  /**
   * Overwrite the build ui method
   */
  buildMethodOverwriteSlot,
  /**
   * component extension.
   */
  componentExtension,
  /**
   * ui logger instance.
   */
  cache,
  /**
   * ui logger instance.
   */
  logger, harmony, startPluginSlot) {
    this.pubsub = pubsub;
    this.config = config;
    this.graphql = graphql;
    this.uiRootSlot = uiRootSlot;
    this.express = express;
    this.preStartSlot = preStartSlot;
    this.onStartSlot = onStartSlot;
    this.publicDirOverwriteSlot = publicDirOverwriteSlot;
    this.buildMethodOverwriteSlot = buildMethodOverwriteSlot;
    this.componentExtension = componentExtension;
    this.cache = cache;
    this.logger = logger;
    this.harmony = harmony;
    this.startPluginSlot = startPluginSlot;
    _defineProperty(this, "_isBundleUiServed", false);
    _defineProperty(this, "runtimeOptions", {});
    /**
     * Events
     */
    _defineProperty(this, "createUiServerStartedEvent", (targetHost, targetPort, uiRoot) => {
      return new (_events().UiServerStartedEvent)(Date.now(), targetHost, targetPort, uiRoot);
    });
  }
  async publicDir(uiRoot) {
    const overwriteFn = this.getOverwritePublic();
    if (overwriteFn) {
      const hasDir = await overwriteFn(uiRoot);
      if (hasDir) return hasDir;
    }
    if (this.config.publicDir.startsWith('/')) {
      return this.config.publicDir.substring(1);
    }
    return this.config.publicDir;
  }
  getUiByName(name) {
    const roots = this.uiRootSlot.toArray();
    const [, root] = roots.find(([, uiRoot]) => {
      return uiRoot.name === name;
    }) || [];
    return root;
  }

  /**
   * create a build of the given UI root.
   */
  async build(uiRootAspectIdOrName, customOutputPath) {
    // TODO: change to MultiStats from webpack once they export it in their types
    this.logger.debug(`build, uiRootAspectIdOrName: "${uiRootAspectIdOrName}"`);
    const maybeUiRoot = this.getUi(uiRootAspectIdOrName);
    if (!maybeUiRoot) throw new (_exceptions().UnknownUI)(uiRootAspectIdOrName, this.possibleUis());
    const [uiRootAspectId, uiRoot] = maybeUiRoot;

    // TODO: @uri refactor all dev server related code to use the bundler extension instead.
    const ssr = uiRoot.buildOptions?.ssr || false;
    const mainEntry = await this.generateRoot(await uiRoot.resolveAspects(_ui().UIRuntime.name), uiRootAspectId);
    const outputPath = customOutputPath || uiRoot.path;
    const browserConfig = (0, _webpackBrowser().default)(outputPath, [mainEntry], uiRoot.name, await this.publicDir(uiRoot));
    const ssrConfig = ssr && (0, _webpackSsr().default)(outputPath, [mainEntry], await this.publicDir(uiRoot));
    const config = [browserConfig, ssrConfig].filter(x => !!x);
    const compiler = (0, _webpack().default)(config);
    this.logger.debug(`build, uiRootAspectIdOrName: "${uiRootAspectIdOrName}" running webpack`);
    const compilerRun = (0, _util().promisify)(compiler.run.bind(compiler));
    const results = await compilerRun();
    this.logger.debug(`build, uiRootAspectIdOrName: "${uiRootAspectIdOrName}" completed webpack`);
    if (!results) throw new (_exceptions().UnknownBuildError)();
    if (results?.hasErrors()) {
      this.clearConsole();
      throw new Error(results?.toString());
    }
    return results;
  }
  registerStartPlugin(startPlugin) {
    this.startPluginSlot.register(startPlugin);
    return this;
  }
  async initiatePlugins(options) {
    const plugins = this.startPluginSlot.values();
    await (0, _pMapSeries().default)(plugins, plugin => plugin.initiate(options));
    return plugins;
  }
  /**
   * create a Bit UI runtime.
   */
  async createRuntime(runtimeOptions) {
    this.runtimeOptions = runtimeOptions;
    const {
      uiRootName,
      pattern,
      dev,
      port,
      rebuild,
      verbose,
      skipUiBuild
    } = this.runtimeOptions;
    // uiRootName to be deprecated
    const uiRootAspectIdOrName = uiRootName || runtimeOptions.uiRootAspectIdOrName;
    const maybeUiRoot = this.getUi(uiRootAspectIdOrName);
    if (!maybeUiRoot) throw new (_exceptions().UnknownUI)(uiRootAspectIdOrName, this.possibleUis());
    const [uiRootAspectId, uiRoot] = maybeUiRoot;
    const plugins = await this.initiatePlugins({
      verbose,
      pattern
    });
    if (this.componentExtension.isHost(uiRootAspectId)) this.componentExtension.setHostPriority(uiRootAspectId);
    const publicDir = await this.publicDir(uiRoot);
    const uiServer = _uiServer().UIServer.create({
      express: this.express,
      graphql: this.graphql,
      uiRoot,
      uiRootExtension: uiRootAspectId,
      ui: this,
      logger: this.logger,
      publicDir,
      startPlugins: plugins
    });

    // Adding signal listeners to make sure we immediately close the process on sigint / sigterm (otherwise webpack dev server closing will take time)
    this.addSignalListener();
    if (dev) {
      await uiServer.dev({
        portRange: port || this.config.portRange
      });
    } else {
      if (!skipUiBuild) await this.buildUI(uiRootAspectId, uiRoot, rebuild);
      const bundleUiPath = this.getBundleUiPath(uiRootAspectId);
      const bundleUiPublicPath = bundleUiPath ? (0, _path().join)(bundleUiPath, publicDir) : undefined;
      const bundleUiRoot = this._isBundleUiServed && bundleUiPublicPath && (0, _fs().existsSync)(bundleUiPublicPath || '') ? bundleUiPublicPath : undefined;
      if (bundleUiRoot) this.logger.debug(`UI createRuntime of ${uiRootAspectId}, bundle will be served from ${bundleUiRoot}`);
      await uiServer.start({
        portRange: port || this.config.portRange,
        bundleUiRoot
      });
    }
    this.pubsub.pub(_ui().UIAspect.id, this.createUiServerStartedEvent(this.config.host, uiServer.port, uiRoot));
    return uiServer;
  }
  addSignalListener() {
    process.on('SIGTERM', () => {
      process.exit();
    });
    process.on('SIGINT', () => {
      process.exit();
    });
  }
  async getPort(port) {
    if (port) return port;
    return this.config.port || this.selectPort();
  }
  /**
   * pre-start events are triggered and *completed* before the webserver started.
   * (the promise is awaited)
   */
  registerPreStart(preStartFn) {
    this.preStartSlot.register(preStartFn);
  }

  /**
   * bind to ui server start event.
   */
  registerOnStart(onStartFn) {
    this.onStartSlot.register(onStartFn);
    return this;
  }

  /**
   * overwrite the build ui function
   */
  registerBuildUIOverwrite(fn) {
    this.buildMethodOverwriteSlot.register(fn);
    return this;
  }

  /**
   * overwrite the build ui function
   */
  registerPublicDirOverwrite(fn) {
    this.publicDirOverwriteSlot.register(fn);
    return this;
  }
  getOverwriteBuildFn() {
    const buildMethodOverwrite = this.buildMethodOverwriteSlot.toArray();
    if (buildMethodOverwrite[0]) {
      const [, fn] = buildMethodOverwrite[0];
      return fn;
    }
    return undefined;
  }
  getOverwritePublic() {
    const overwritePublic = this.publicDirOverwriteSlot.toArray();
    if (overwritePublic[0]) {
      const [, fn] = overwritePublic[0];
      return fn;
    }
    return undefined;
  }
  async invokePreStart(preStartOpts) {
    const onPreStartFuncs = this.preStartSlot.values();
    await (0, _pMapSeries().default)(onPreStartFuncs, async fn => fn(preStartOpts));
  }
  async invokeOnStart() {
    const onStartFuncs = this.onStartSlot.values();
    const startPlugins = await (0, _pMapSeries().default)(onStartFuncs, async fn => fn());
    return startPlugins.filter(plugin => !!plugin);
  }

  /**
   * register a UI slot.
   */
  registerUiRoot(uiRoot) {
    return this.uiRootSlot.register(uiRoot);
  }

  /**
   * get a UI runtime instance.
   */
  getUi(uiRootAspectIdOrName) {
    if (uiRootAspectIdOrName) {
      const root = this.uiRootSlot.get(uiRootAspectIdOrName) || this.getUiByName(uiRootAspectIdOrName);
      if (!root) return undefined;
      return [uiRootAspectIdOrName, root];
    }
    const uis = this.uiRootSlot.toArray();
    if (uis.length === 1) return uis[0];
    return uis.find(([, root]) => root.priority);
  }
  isHostAvailable() {
    return Boolean(this.componentExtension.getHost());
  }
  getUiName(uiRootAspectIdOrName) {
    const [, ui] = this.getUi(uiRootAspectIdOrName) || [];
    if (!ui) return undefined;
    return ui.name;
  }
  possibleUis() {
    return this.uiRootSlot.toArray().map(([id]) => id);
  }
  createLink(aspectDefs, rootExtensionName) {
    return (0, _harmonyModules().createRoot)(aspectDefs, rootExtensionName);
  }

  /**
   * generate the root file of the UI runtime.
   */
  async generateRoot(aspectDefs, rootExtensionName, runtimeName = _ui().UIRuntime.name, rootAspect = _ui().UIAspect.id, config, path, ignoreVersion, addRuntimes, harmonyPackage, shouldRun = false) {
    const contents = await (0, _harmonyModules().createRoot)(aspectDefs, rootExtensionName, rootAspect, runtimeName, config || this.harmony.config.toObject(), ignoreVersion, addRuntimes, harmonyPackage, shouldRun);
    const filepath = (0, _path().resolve)((0, _path().join)(path || __dirname, `${runtimeName}.root${(0, _utils().sha1)(contents)}.js`));
    if (_fsExtra().default.existsSync(filepath)) return filepath;
    _fsExtra().default.outputFileSync(filepath, contents);
    return filepath;
  }
  async selectPort() {
    const [from, to] = this.config.portRange;
    const usedPorts = (await this.cache.get(`${from}${to}`)) || [];
    const port = await _toolboxNetwork().Port.getPort(from, to, usedPorts);
    // this will lock the port for 1 min to avoid race conditions
    await this.cache.set(`${from}${to}`, usedPorts.concat(port), 5000);
    return port;
  }
  async buildUI(uiRootAspectId, uiRoot, rebuild) {
    this.logger.debug(`buildUI, uiRootAspectId ${uiRootAspectId}`);
    const overwrite = this.getOverwriteBuildFn();
    if (overwrite) return overwrite(uiRootAspectId, uiRoot, rebuild);
    this._isBundleUiServed = await this.shouldServeBundleUi(uiRootAspectId, uiRoot, rebuild);
    await this.buildIfChanged(uiRootAspectId, uiRoot, rebuild);
    await this.buildIfNoBundle(uiRootAspectId, uiRoot);
    return '';
  }
  async shouldServeBundleUi(uiRootAspectId, uiRoot, force) {
    if (!uiRoot.buildOptions?.prebundle) {
      return false;
    }
    const currentBundleUiHash = await this.createBundleUiHash(uiRoot);
    const cachedBundleUiHash = this.readBundleUiHash(uiRootAspectId);
    const isLocalBuildAvailable = (0, _fs().existsSync)((0, _path().join)(uiRoot.path, await this.publicDir(uiRoot)));
    return currentBundleUiHash === cachedBundleUiHash && !isLocalBuildAvailable && !force;
  }
  async buildIfChanged(uiRootAspectId, uiRoot, force) {
    this.logger.debug(`buildIfChanged, uiRootAspectId ${uiRootAspectId}`);
    if (this._isBundleUiServed) {
      this.logger.debug(`buildIfChanged, uiRootAspectId ${uiRootAspectId}, returned from ui bundle cache`);
      return false;
    }
    const currentBuildUiHash = await this.createBuildUiHash(uiRoot);
    const cachedBuildUiHash = await this.cache.get(uiRoot.path);
    if (currentBuildUiHash === cachedBuildUiHash && !force) {
      this.logger.debug(`buildIfChanged, uiRootAspectId ${uiRootAspectId}, returned from ui build cache`);
      return false;
    }
    if (!cachedBuildUiHash) {
      this.logger.console(`Building UI assets for '${_chalk().default.cyan(uiRoot.name)}' in target directory: ${_chalk().default.cyan(await this.publicDir(uiRoot))}. The first time we build the UI it may take a few minutes.`);
    } else {
      this.logger.console(`Rebuilding UI assets for '${_chalk().default.cyan(uiRoot.name)} in target directory: ${_chalk().default.cyan(await this.publicDir(uiRoot))}' as ${uiRoot.configFile} has been changed.`);
    }
    await this.build(uiRootAspectId);
    await this.cache.set(uiRoot.path, currentBuildUiHash);
    return true;
  }
  async createBuildUiHash(uiRoot, runtime = 'ui') {
    const aspects = await uiRoot.resolveAspects(runtime);
    aspects.sort((a, b) => a.aspectPath > b.aspectPath ? 1 : -1);
    const aspectPathStrings = aspects.map(aspect => {
      return [aspect.aspectPath, aspect.runtimePath].join('');
    });
    return (0, _utils().sha1)(aspectPathStrings.join(''));
  }

  /**
   * Generate hash for a given root
   * This API is public and used by external users, do not rename this function
   */
  async buildUiHash(uiRoot, runtime = 'ui') {
    return this.createBuildUiHash(uiRoot, runtime);
  }
  async createBundleUiHash(uiRoot, runtime = 'ui') {
    const aspects = await uiRoot.resolveAspects(runtime);
    aspects.sort((a, b) => (a.getId || a.aspectPath) > (b.getId || b.aspectPath) ? 1 : -1);
    const aspectIds = aspects.map(aspect => aspect.getId || aspect.aspectPath);
    return (0, _utils().sha1)(aspectIds.join(''));
  }
  readBundleUiHash(uiRootAspectId) {
    const bundleUiPathFromBvm = this.getBundleUiPath(uiRootAspectId);
    if (!bundleUiPathFromBvm) {
      return '';
    }
    const hashFilePath = (0, _path().join)(bundleUiPathFromBvm, _bundleUi().BUNDLE_UI_HASH_FILENAME);
    if ((0, _fs().existsSync)(hashFilePath)) {
      return (0, _fs().readFileSync)(hashFilePath).toString();
    }
    return '';
  }
  getBundleUiPath(uiRootAspectId) {
    try {
      const uiPathFromBvm = (0, _aspectLoader().getAspectDirFromBvm)(_ui().UIAspect.id);
      return (0, _path().join)(uiPathFromBvm, _bundleUi().BundleUiTask.getArtifactDirectory(uiRootAspectId));
    } catch (err) {
      this.logger.error(`getBundleUiPath, getAspectDirFromBvm failed with err: ${err}`);
      return undefined;
    }
  }
  async buildIfNoBundle(uiRootAspectId, uiRoot) {
    if (this._isBundleUiServed) return false;
    const config = (0, _webpackBrowser().default)(uiRoot.path, [await this.generateRoot(await uiRoot.resolveAspects(_ui().UIRuntime.name), uiRootAspectId)], uiRoot.name, await this.publicDir(uiRoot));
    if (config.output?.path && _fsExtra().default.pathExistsSync(config.output.path)) return false;
    const hash = await this.createBuildUiHash(uiRoot);
    await this.build(uiRootAspectId);
    await this.cache.set(uiRoot.path, hash);
    return true;
  }
  clearConsole() {
    process.stdout.write(process.platform === 'win32' ? '\x1B[2J\x1B[0f' : '\x1B[2J\x1B[3J\x1B[H');
  }
  get publicUrl() {
    return this.config.publicUrl;
  }
  static async provider([pubsub, cli, graphql, express, componentExtension, cache, loggerMain], config, [uiRootSlot, preStartSlot, onStartSlot, publicDirOverwriteSlot, buildMethodOverwriteSlot, proxyGetterSlot], harmony) {
    // aspectExtension.registerRuntime(new RuntimeDefinition('ui', []))
    const logger = loggerMain.createLogger(_ui().UIAspect.id);
    const ui = new UiMain(pubsub, config, graphql, uiRootSlot, express, preStartSlot, onStartSlot, publicDirOverwriteSlot, buildMethodOverwriteSlot, componentExtension, cache, logger, harmony, proxyGetterSlot);
    cli.register(new (_start().StartCmd)(ui, logger), new (_uiBuild().UIBuildCmd)(ui));
    return ui;
  }
}
exports.UiMain = UiMain;
_defineProperty(UiMain, "defaultConfig", {
  publicDir: 'public/bit',
  portRange: [3000, 3100],
  host: 'localhost'
});
_defineProperty(UiMain, "runtime", _cli().MainRuntime);
_defineProperty(UiMain, "dependencies", [_pubsub().PubsubAspect, _cli().CLIAspect, _graphql().GraphqlAspect, _express().ExpressAspect, _component().ComponentAspect, _cache().CacheAspect, _logger().LoggerAspect]);
_defineProperty(UiMain, "slots", [_harmony().Slot.withType(), _harmony().Slot.withType(), _harmony().Slot.withType(), _harmony().Slot.withType(), _harmony().Slot.withType(), _harmony().Slot.withType()]);
_ui().UIAspect.addRuntime(UiMain);

//# sourceMappingURL=ui.main.runtime.js.map