"use strict";

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

function _react() {
  const data = _interopRequireDefault(require("react"));

  _react = function _react() {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function _chalk() {
    return data;
  };

  return data;
}

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

  _path = function _path() {
    return data;
  };

  return data;
}

function _assert() {
  const data = _interopRequireDefault(require("assert"));

  _assert = function _assert() {
    return data;
  };

  return data;
}

function _mkdirp() {
  const data = _interopRequireDefault(require("mkdirp"));

  _mkdirp = function _mkdirp() {
    return data;
  };

  return data;
}

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

  _lodash = function _lodash() {
    return data;
  };

  return data;
}

function _signale() {
  const data = _interopRequireDefault(require("signale"));

  _signale = function _signale() {
    return data;
  };

  return data;
}

function _umiUtils() {
  const data = require("umi-utils");

  _umiUtils = function _umiUtils() {
    return data;
  };

  return data;
}

function _error() {
  const data = require("umi-core/lib/error");

  _error = function _error() {
    return data;
  };

  return data;
}

var _getPaths = _interopRequireDefault(require("./getPaths"));

var _getPlugins = _interopRequireDefault(require("./getPlugins"));

var _PluginAPI = _interopRequireDefault(require("./PluginAPI"));

var _UserConfig = _interopRequireDefault(require("./UserConfig"));

var _registerBabel = _interopRequireDefault(require("./registerBabel"));

var _getCodeFrame = _interopRequireDefault(require("./utils/getCodeFrame"));

var _writeContent = _interopRequireDefault(require("./utils/writeContent"));

var _getRouteManager = _interopRequireDefault(require("./plugins/commands/getRouteManager"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _createForOfIteratorHelper(o) { if (typeof Symbol === "undefined" || o[Symbol.iterator] == null) { if (Array.isArray(o) || (o = _unsupportedIterableToArray(o))) { var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var it, normalCompletion = true, didErr = false, err; return { s: function s() { it = o[Symbol.iterator](); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }

function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }

function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

const debug = require('debug')('umi-build-dev:Service');

class Service {
  constructor({
    cwd
  }) {
    //  用户传入的 cmd 不可信任 转化一下
    this.cwd = cwd || process.cwd();

    try {
      this.pkg = require((0, _path().join)(this.cwd, 'package.json')); // eslint-disable-line
    } catch (e) {
      this.pkg = {};
    }

    (0, _registerBabel.default)({
      cwd: this.cwd
    });
    this.commands = {};
    this.pluginHooks = {};
    this.pluginMethods = {};
    this.generators = {};
    this.UmiError = _error().UmiError; // resolve user config

    this.config = _UserConfig.default.getConfig({
      cwd: this.cwd,
      service: this
    });
    debug(`user config: ${JSON.stringify(this.config)}`); // resolve plugins

    this.plugins = this.resolvePlugins();
    this.extraPlugins = [];
    debug(`plugins: ${this.plugins.map(p => p.id).join(' | ')}`); // resolve paths

    this.paths = (0, _getPaths.default)(this);
  }

  printUmiError(error, opts) {
    this.applyPlugins('onPrintUmiError', {
      args: {
        error,
        opts
      }
    });
    (0, _error().printUmiError)(error, opts);
  }

  resolvePlugins() {
    try {
      (0, _assert().default)(Array.isArray(this.config.plugins || []), `Configure item ${_chalk().default.underline.cyan('plugins')} should be Array, but got ${_chalk().default.red(typeof this.config.plugins)}`);
      return (0, _getPlugins.default)({
        cwd: (0, _umiUtils().winPath)(this.cwd),
        plugins: this.config.plugins || []
      });
    } catch (e) {
      if (process.env.UMI_TEST || process.env.UMI_UI) {
        throw new Error(e);
      } else {
        this.printUmiError(e);
        process.exit(1);
      }
    }
  }

  initPlugin(plugin) {
    const id = plugin.id,
          apply = plugin.apply,
          opts = plugin.opts;

    try {
      (0, _assert().default)(typeof apply === 'function', `
plugin must export a function, e.g.

  export default function(api) {
    // Implement functions via api
  }
        `.trim());
      const api = new Proxy(new _PluginAPI.default(id, this), {
        get: (target, prop) => {
          if (this.pluginMethods[prop]) {
            return this.pluginMethods[prop];
          }

          if ([// methods
          'changePluginOption', 'applyPlugins', '_applyPluginsAsync', 'writeTmpFile', 'getRoutes', 'getRouteComponents', // properties
          'cwd', 'config', 'webpackConfig', 'pkg', 'paths', 'routes', // error handler
          'UmiError', 'printUmiError', // dev methods
          'restart', 'printError', 'printWarn', 'refreshBrowser', 'rebuildTmpFiles', 'rebuildHTML'].includes(prop)) {
            if (typeof this[prop] === 'function') {
              return this[prop].bind(this);
            } else {
              return this[prop];
            }
          } else {
            return target[prop];
          }
        }
      });

      api.onOptionChange = fn => {
        (0, _assert().default)(typeof fn === 'function', `The first argument for api.onOptionChange should be function in ${id}.`);
        plugin.onOptionChange = fn;
      };

      apply(api, opts);
      plugin._api = api;
    } catch (e) {
      if (process.env.UMI_TEST) {
        throw new Error(e);
      } else {
        _signale().default.error(`
Plugin ${_chalk().default.cyan.underline(id)} initialize failed

${(0, _getCodeFrame.default)(e, {
          cwd: this.cwd
        })}
        `.trim());

        console.error(e);
        process.exit(1);
      }
    }
  }

  initPlugins() {
    // Plugin depth
    let count = 0;

    const initExtraPlugins = () => {
      if (!this.extraPlugins.length) {
        return;
      }

      const extraPlugins = (0, _lodash().cloneDeep)(this.extraPlugins);
      this.extraPlugins = [];
      extraPlugins.forEach(plugin => {
        this.initPlugin(plugin);
        this.plugins.push(plugin);
        initExtraPlugins();
      });
      count += 1;
      (0, _assert().default)(count <= 10, `插件注册死循环？`);
    };

    const plugins = (0, _lodash().cloneDeep)(this.plugins);
    this.plugins = [];
    plugins.forEach(plugin => {
      this.initPlugin(plugin);
      this.plugins.push(plugin); // reset count

      count = 0;
      initExtraPlugins();
    }); // Throw error for methods that can't be called after plugins is initialized

    this.plugins.forEach(plugin => {
      ['onOptionChange', 'register', 'registerMethod', 'registerPlugin'].forEach(method => {
        plugin._api[method] = () => {
          throw new Error(`api.${method}() should not be called after plugin is initialized.`);
        };
      });
    });
  }

  changePluginOption(id, newOpts) {
    (0, _assert().default)(id, `id must supplied`);
    const plugin = this.plugins.filter(p => p.id === id)[0];
    (0, _assert().default)(plugin, `plugin ${id} not found`);
    plugin.opts = newOpts;

    if (plugin.onOptionChange) {
      plugin.onOptionChange(newOpts);
    } else {
      this.restart(`plugin ${id}'s option changed`);
    }
  }

  applyPlugins(key, opts = {}) {
    debug(`apply plugins ${key}`);
    return (this.pluginHooks[key] || []).reduce((memo, {
      fn
    }) => {
      try {
        return fn({
          memo,
          args: opts.args
        });
      } catch (e) {
        console.error(_chalk().default.red(`Plugin apply failed: ${e.message}`));
        throw e;
      }
    }, opts.initialValue);
  }

  _applyPluginsAsync(key, opts = {}) {
    var _this = this;

    return _asyncToGenerator(function* () {
      debug(`apply plugins async ${key}`);
      const hooks = _this.pluginHooks[key] || [];
      let memo = opts.initialValue;

      var _iterator = _createForOfIteratorHelper(hooks),
          _step;

      try {
        for (_iterator.s(); !(_step = _iterator.n()).done;) {
          const hook = _step.value;
          const fn = hook.fn; // eslint-disable-next-line no-await-in-loop

          memo = yield fn({
            memo,
            args: opts.args
          });
        }
      } catch (err) {
        _iterator.e(err);
      } finally {
        _iterator.f();
      }

      return memo;
    })();
  }

  loadEnv() {
    const basePath = (0, _path().join)(this.cwd, '.env');
    const localPath = `${basePath}.local`;
    (0, _umiUtils().loadDotEnv)(basePath);
    (0, _umiUtils().loadDotEnv)(localPath);
  }

  writeTmpFile(file, content) {
    const paths = this.paths;
    const path = (0, _path().join)(paths.absTmpDirPath, file);

    _mkdirp().default.sync((0, _path().dirname)(path));

    (0, _writeContent.default)(path, content);
  }

  getRoutes() {
    const RoutesManager = (0, _getRouteManager.default)(this);
    RoutesManager.fetchRoutes();
    return RoutesManager.routes;
  }

  getRouteComponents() {
    const routes = this.getRoutes();

    const getComponents = routes => {
      return routes.reduce((memo, route) => {
        if (route.component && !route.component.startsWith('()')) {
          const component = (0, _path().isAbsolute)(route.component) ? route.component : require.resolve((0, _path().join)(this.cwd, route.component));
          memo.push((0, _umiUtils().winPath)(component));
        }

        if (route.routes) {
          memo = memo.concat(getComponents(route.routes));
        }

        return memo;
      }, []);
    };

    return (0, _lodash().uniq)(getComponents(routes));
  }

  init() {
    // load env
    this.loadEnv(); // init plugins

    this.initPlugins(); // reload user config

    const userConfig = new _UserConfig.default(this);
    const config = userConfig.getConfig({
      force: true
    });
    mergeConfig(this.config, config);
    this.userConfig = userConfig;

    if (config.browserslist) {
      (0, _umiUtils().deprecate)('config.browserslist', 'use config.targets instead');
    }

    debug('got user config');
    debug(this.config); // assign user's outputPath config to paths object

    if (config.outputPath) {
      const paths = this.paths;
      paths.outputPath = config.outputPath;
      paths.absOutputPath = (0, _path().join)(paths.cwd, config.outputPath);
    }

    debug('got paths');
    debug(this.paths);
  }

  registerCommand(name, opts, fn) {
    if (typeof opts === 'function') {
      fn = opts;
      opts = null;
    }

    opts = opts || {};
    (0, _assert().default)(!(name in this.commands), `Command ${name} exists, please select another one.`);
    this.commands[name] = {
      fn,
      opts
    };
  }

  run(name = 'help', args) {
    this.init();
    return this.runCommand(name, args);
  }

  runCommand(rawName, rawArgs = {}, remoteLog) {
    debug(`raw command name: ${rawName}, args: ${JSON.stringify(rawArgs)}`);

    const _this$applyPlugins = this.applyPlugins('_modifyCommand', {
      initialValue: {
        name: rawName,
        args: rawArgs
      }
    }),
          name = _this$applyPlugins.name,
          args = _this$applyPlugins.args;

    debug(`run ${name} with args ${JSON.stringify(args)}`);
    const command = this.commands[name];

    if (!command) {
      _signale().default.error(`Command ${_chalk().default.underline.cyan(name)} does not exists`);

      process.exit(1);
    }

    const fn = command.fn,
          opts = command.opts;

    if (opts.webpack) {
      // webpack config
      this.webpackConfig = require('./getWebpackConfig').default(this, {
        watch: rawArgs.w || rawArgs.watch
      });

      if (this.config.ssr) {
        // when use ssr, push client-manifest plugin into client webpackConfig
        this.webpackConfig.plugins.push(new (require('./plugins/commands/getChunkMapPlugin').default(this))()); // server webpack config

        this.ssrWebpackConfig = require('./getWebpackConfig').default(this, {
          ssr: this.config.ssr
        });
      }
    }

    return fn(args, {
      remoteLog
    });
  }

}

exports.default = Service;

function mergeConfig(oldConfig, newConfig) {
  Object.keys(oldConfig).forEach(key => {
    if (!(key in newConfig)) {
      delete oldConfig[key];
    }
  });
  (0, _lodash().assign)(oldConfig, newConfig);
  return oldConfig;
}