"use strict";

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

var _debug = _interopRequireDefault(require("debug"));

var _path = require("path");

var _fsExtra = require("fs-extra");

var plugins = _interopRequireWildcard(require("./plugins"));

var _scanner = require("./scanner");

var _events = _interopRequireDefault(require("../core/events"));

var _tree = require("../commands/tree");

function _getRequireWildcardCache() { if (typeof WeakMap !== "function") return null; var cache = new WeakMap(); _getRequireWildcardCache = function () { return cache; }; return cache; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }

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

/*
 * Copyright 2017-19 IBM Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
var __awaiter = void 0 && (void 0).__awaiter || function (thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function (resolve) {
      resolve(value);
    });
  }

  return new (P || (P = Promise))(function (resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }

    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }

    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }

    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};

const debug = (0, _debug.default)('core/plugins/assembler');
/**
 * Return the location of the serialized `PrescanModel` that
 * represents the model that ships with a client (as opposed to the
 * model that represents user-installed plugins)
 *
 */

const prescanned = () => require.resolve('@kui-shell/prescan');
/**
 * Serialize a `PrescanModel` to disk
 *
 */


const writePrescanned = (modules, destFile = prescanned()) => __awaiter(void 0, void 0, void 0, function* () {
  debug('writePrescanned', process.cwd(), destFile, modules);
  const str = JSON.stringify(modules);
  yield (0, _fsExtra.ensureFile)(destFile);
  yield (0, _fsExtra.writeFile)(destFile, str);
});
/**
 * Make a tree out of a flat map.
 * e.g. take "/wsk" and "/wsk/actions" and make a tree out of that flat
 * structure based on the "/path/hierarchy"
 *
 */


const makeTree = (map, docs) => {
  const keys = Object.keys(map);

  if (keys.length === 0) {
    debug('interesting, not a single command registered a usage model'); // this isn't the end of the world, but probably a sign of
    // incomplete plugin design; so let's warn the developer (note
    // that this command is executed as part of plugin precompilation
    // so the user in this case is the plugin developer)

    console.error('Warning: none of your commands registered a usage model');
    return {};
  } // sort the keys lexicographically


  keys.sort();
  /** create new node */

  const newLeaf = route => ({
    route
  });

  const newNode = route => Object.assign(newLeaf(route), {
    children: {}
  });
  /** get or create a subtree */


  const getOrCreate = (tree, pathPrefix) => {
    if (!tree.children) {
      tree.children = {};
    }

    const entry = tree.children[pathPrefix];

    if (!entry) {
      tree.children[pathPrefix] = newNode(pathPrefix);
      return tree.children[pathPrefix];
    } else {
      return entry;
    }
  };

  const tree = keys.reduce((tree, route) => {
    const split = route.split(/\//);
    let subtree = tree;

    for (let idx = 0; idx < split.length; idx++) {
      const pathPrefix = split.slice(0, idx).join('/');
      subtree = getOrCreate(subtree, pathPrefix);
    }

    if (!subtree.children) subtree.children = {};
    const leaf = subtree.children[route] = newLeaf(route);
    leaf.usage = map[route];
    leaf.docs = leaf.usage && leaf.usage.header || docs[route];
    return tree;
  }, newNode('/'));
  return tree.children[''].children[''].children;
};
/**
 * Scan the registered commands for usage docs, so that we can stash
 * them away in the compiled plugin registry. This will allow us to
 * present docs in a general way, not only in response to evaluation
 * of commands.
 *
 */


const amendWithUsageModels = modules => {
  modules.docs = {};
  modules.usage = {};
  (0, _tree.getModel)().forEachNode(({
    route,
    options,
    synonyms
  }) => {
    if (options && options.usage) {
      modules.usage[route] = Object.assign({
        route
      }, options.usage);
      if (options.needsUI) modules.usage[route].needsUI = true;
      if (options.requiresLocal) modules.usage[route].requiresLocal = true; // if (options.noAuthOk) modules.usage[route].noAuthOk = true

      if (options.synonymFor) modules.usage[route].synonymFor = options.synonymFor.route;
      if (synonyms) modules.usage[route].synonyms = Object.keys(synonyms).map(route => synonyms[route].key);

      if (options.usage.docs) {
        modules.docs[route] = options.usage.docs;
      }
    }

    if (options && options.docs) {
      modules.docs[route] = options.docs;
    }
  }); // modules.usage right not is flat, i.e. it may contain entries
  // for "/wsk" and "/wsk/actions"; make a tree out of that flat
  // structure based on the "/path/hierarchy"

  modules.usage = makeTree(modules.usage, modules.docs);
  return modules;
};
/**
 * Assemble the plugin `PrescanModel`, and serialize it to disk. This
 * code is called both for assembling the model that is shipped with a
 * client, and (via `compileUserInstalled`, below) for assembling, on
 * the fly, the registry for user-installed plugins.
 *
 */


const compile = (pluginRoot = process.env.PLUGIN_ROOT || (0, _path.join)(__dirname, plugins.pluginRoot), externalOnly = false) => __awaiter(void 0, void 0, void 0, function* () {
  debug('pluginRoot is %s', pluginRoot);
  debug('externalOnly is %s', externalOnly);
  (0, _tree.initIfNeeded)();
  const modules = yield (0, _scanner.assemble)(plugins.registrar, {
    externalOnly,
    pluginRoot
  });
  debug('modules', modules);
  /** make the paths relative to the root directory */

  const fixupOnePath = filepath => {
    // NOTE ON relativization: this is important so that webpack can
    // be instructed to pull in the plugins into the build see the
    // corresponding NOTE in ./plugins.ts and ./preloader.ts
    const pattern = new RegExp(`^(.*\\${_path.sep})(plugin-.*)$`);
    const fixed = externalOnly ? filepath : (0, _path.relative)(pluginRoot, filepath).replace(pattern, '$2');
    return fixed;
  };

  const fixupPaths = pluginList => pluginList.map(plugin => Object.assign(plugin, {
    path: fixupOnePath(plugin.path)
  }));

  const model = Object.assign(modules, {
    preloads: fixupPaths(modules.preloads),
    flat: fixupPaths(modules.flat)
  });
  const modelWithUsage = amendWithUsageModels(model);
  const destFile = externalOnly ? (0, _path.join)(pluginRoot, '@kui-shell/prescan.json') : prescanned();
  yield Promise.all([writePrescanned(modelWithUsage, destFile)]);

  if (externalOnly) {
    // then this is a live install, we need to smash it into the live model
    plugins._useUpdatedUserPrescan(modelWithUsage);
  }
});
/**
 * The entry point for recompiling the user-installed plugin registry
 *
 */


exports.compile = compile;

function compileUserInstalled(pluginToBeRemoved) {
  return __awaiter(this, void 0, void 0, function* () {
    debug('compileUserInstalled', pluginToBeRemoved);
    const home = yield plugins.userInstalledHome();
    const pluginRoot = (0, _path.join)(home, 'node_modules');
    yield compile(pluginRoot, true);

    _events.default.emit('/plugin/compile/done');
  });
}

var _default = compile;
exports.default = _default;
debug('loading done');