cli/start.js

import os from 'os'
import path from 'path'
import fs from 'fs'
import util from '../util'
import chalk from 'chalk'
import nodemon from 'nodemon'
import { monitorCtrlC } from 'monitorctrlc'
import { execSync } from 'child_process'

const getPath = path => {
  if (os.platform() === 'win32') {
    const shortPath = execSync(`@echo off && for %I in ("${path}") do echo %~sI`)
    return shortPath.toString('utf8').replace(/(\n|\r)+$/, '')
  } else {
    return path
  }
}

/**
 * Entry point of botpress
 *
 * It will do the following things:
 *
 * 1. Find botpress instance creator in `node_modules` folder in current project.
 * 2. Find the `botfile.js` which will be injected into the creator to create the instance.
 * 3. Start the botpress instance.
 */
module.exports = (projectPath, options) => {
  let Botpress = null

  if (!projectPath || typeof projectPath !== 'string') {
    projectPath = '.'
  }

  projectPath = path.resolve(projectPath)

  try {
    // eslint-disable-next-line no-eval
    Botpress = eval('require')(path.join(projectPath, 'node_modules', 'botpress')).Botpress()
  } catch (err) {
    util.print('error', err.message)
    util.print('error', err.stack)
    util.print('error', '(fatal) Could not load the local version of botpress')
    util.print('Hint: 1) have you used `botpress init` to create a new bot the proper way?')
    util.print('Hint: 2) Do you have read and write permissions on the current directory?')
    util.print('-------------')
    util.print(
      'If none of the above works, this might be a bug in botpress. ' +
        'Please contact the Botpress Team on gitter and provide the printed error above.'
    )
    process.exit(1)
  }

  const botfile = path.join(projectPath, 'botfile.js')
  if (!fs.existsSync(botfile)) {
    util.print('error', `(fatal) No ${chalk.bold('botfile.js')} file found at: ` + botfile)
    process.exit(1)
  }

  const getDefaultWatchIgnore = () => {
    // eslint-disable-next-line no-eval
    const bf = eval('require')(botfile)
    const dataDir = util.getDataLocation(bf.dataDir, projectPath)
    const modulesConfigDir = util.getDataLocation(bf.modulesConfigDir, projectPath)
    return [dataDir, modulesConfigDir, 'node_modules']
  }

  const opts = options.opts()
  if (opts.watch || opts.w) {
    util.print('info', '*** watching files for changes ***')

    const argvWithoutWatch = process.argv.filter(arg => !/^(--watch|-w)$/.test(arg))
    argvWithoutWatch[0] = getPath(argvWithoutWatch[0])

    const nodemonOptions = {
      cwd: process.cwd(),
      exec: argvWithoutWatch.join(' '),
      ext: opts.watchExt,
      watch: opts.watchDir && opts.watchDir.length ? opts.watchDir : undefined,
      ignore: opts.watchIgnore && opts.watchIgnore.length ? opts.watchIgnore : getDefaultWatchIgnore(),
      stdin: false,
      restartable: false
    }

    const mon = nodemon(nodemonOptions)
    mon.on('restart', (changedFile, two) =>
      util.print('info', '*** restarting botpress because of file change: ', changedFile)
    )

    monitorCtrlC(() => {
      mon.emit('quit')
      setTimeout(() => process.exit(), 100)
    })
  } else {
    const bot = new Botpress({ botfile })
    bot.start()
  }
}