class_app.js

import util from 'util'
import lodash from 'lodash'
import Bajo from './bajo.js'
import resolvePath from '../lib/resolve-path.js'
import parseArgsArgv from '../lib/parse-args-argv.js'
import parseEnv from '../lib/parse-env.js'
import {
  buildBaseConfig,
  buildExtConfig,
  buildPlugins,
  collectConfigHandlers,
  bootOrder,
  bootPlugins,
  exitHandler,
  runAsApplet
} from './helper/bajo.js'

const { last } = lodash

/**
 * App class. This is where everything starts, the boot process:
 *
 * 1. Parsing all arguments and environment values
 * 2. Create {@link Bajo|Bajo} instance
 * 3. Building {@link module:class/helper/bajo.buildBaseConfig|base config}
 * 4. {@link module:class/helper/bajo.buildPlugins|Building plugins}
 * 5. Collect all {@link module:class/helper/bajo.collectConfigHandlers|config handler}
 * 6. Building {@link module:class/helper/bajo.buildExtConfig|extra config}
 * 7. Setup {@link module:class/helper/bajo.bootOrder|boot order}
 * 8. {@link module:class/helper/bajo.bootPlugins|Boot loaded plugins}
 * 9. Attach {@link module:class/helper/bajo.exitHandler|exit handlers}
 * 10. Finish
 *
 * After boot process is completed, event ```bajo:afterBootComplete``` is emitted.
 *
 * If app mode is ```applet```, it runs your choosen applet instead.
 *
 * @class
 */
class App {
  /**
   * Class constructor
   *
   * @param {string} cwd - Current working dirctory
   */
  constructor (cwd) {
    if (!cwd) cwd = process.cwd()
    const l = last(process.argv)
    if (l.startsWith('--cwd')) {
      const parts = l.split('=')
      cwd = parts[1]
    }
    this.dir = resolvePath(cwd)
    this.pluginFactory = {}
    process.env.APPDIR = this.dir
  }

  /**
   * Add a plugin to the app
   *
   * @method
   * @param {Object} plugin - A valid bajo plugin
   */
  addPlugin = (plugin, factory) => {
    if (this[plugin.name]) throw new Error(`Plugin '${plugin.name}' added already`)
    this[plugin.name] = plugin
    if (factory) this.pluginFactory[plugin.name] = factory
  }

  /**
   * Get all loaded plugin names
   *
   * @returns {string[]}
   */
  getPluginNames = () => {
    return Object.keys(this.pluginFactory)
  }

  /**
   * Dumping variable on screen
   *
   * @method
   * @param  {...any} args - any arguments passed will be displayed on screen. If the last argument is a boolean 'true', app will quit rightaway
   */
  dump = (...args) => {
    const terminate = last(args) === true
    if (terminate) args.pop()
    for (const arg of args) {
      const result = util.inspect(arg, false, null, true)
      console.log(result)
    }
    if (terminate) process.kill(process.pid, 'SIGINT')
  }

  /**
   * Booting the app
   *
   * @method
   * @async
   */
  boot = async () => {
    // argv/args
    const { args, argv } = await parseArgsArgv.call(this.app) ?? {}
    this.argv = argv
    this.args = args
    this.env = parseEnv() ?? {}

    const bajo = new Bajo(this)
    await buildBaseConfig.call(bajo)
    await buildPlugins.call(bajo)
    await collectConfigHandlers.call(bajo)
    await buildExtConfig.call(bajo)
    await bootOrder.call(bajo)
    await bootPlugins.call(bajo)
    await exitHandler.call(bajo)
    // boot complete
    const elapsed = new Date() - bajo.runAt
    bajo.log.info('bootCompleted%s', bajo.lib.aneka.secToHms(elapsed, true))
    await bajo.runHook('bajo:afterBootComplete')
    if (bajo.applet) await runAsApplet.call(bajo)
  }
}

export default App