Exemple #1
0
 before(async () => {
   await runscript(`tsc -p ${baseDir}/tsconfig.json`, { cwd: baseDir });
   const dest = path.join(baseDir, 'node_modules/egg');
   await rimraf(dest);
   await mkdirp(path.dirname(dest));
   fs.symlinkSync('../../../../../', dest);
 });
function* getRotatelog(logfile) {
  yield mkdirp(path.dirname(logfile));

  if (yield fs.exists(logfile)) {
    // format style: .20150602.193100
    const timestamp = moment().format('.YYYYMMDD.HHmmss');
    // Note: rename last log to next start time, not when last log file created
    yield fs.rename(logfile, logfile + timestamp);
  }

  return yield fs.open(logfile, 'a');
}
  * run(context) {
    const { argv, env, cwd, execArgv } = context;
    // C:\Users\DW\  win用户下
    const HOME = homedir();
    const logDir = path.join(HOME, 'logs');

    // egg-script start
    // egg-script start ./server
    // egg-script start /opt/app
    let baseDir = argv._[0] || cwd;
    if (!path.isAbsolute(baseDir)) baseDir = path.join(cwd, baseDir);
    argv.baseDir = baseDir;

    const isDaemon = argv.daemon;
    // start 命令中,没有framework参数。egg-utils的方法返回启动框架的node包目录
    // pkg中优先检查是否有egg,framework字段即上层框架。其次egg目录。
    argv.framework = yield this.getFrameworkPath({
      framework: argv.framework,
      baseDir,
    });

    this.frameworkName = yield this.getFrameworkName(argv.framework);

    const pkgInfo = require(path.join(baseDir, 'package.json'));
    argv.title = argv.title || `egg-server-${pkgInfo.name}`;

    argv.stdout = argv.stdout || path.join(logDir, 'master-stdout.log');
    argv.stderr = argv.stderr || path.join(logDir, 'master-stderr.log');

    // normalize env
    env.HOME = HOME;
    env.NODE_ENV = 'production';

    env.PATH = [
      // for nodeinstall
      path.join(baseDir, 'node_modules/.bin'),
      // support `.node/bin`, due to npm5 will remove `node_modules/.bin`
      path.join(baseDir, '.node/bin'),
      // adjust env for win
      env.PATH || env.Path,
    ].filter(x => !!x).join(path.delimiter);

    // for alinode
    env.ENABLE_NODE_LOG = 'YES';
    env.NODE_LOG_DIR = env.NODE_LOG_DIR || path.join(logDir, 'alinode');
    yield mkdirp(env.NODE_LOG_DIR);

    // cli argv -> process.env.EGG_SERVER_ENV -> `undefined` then egg will use `prod`
    if (argv.env) {
      // if undefined, should not pass key due to `spwan`, https://github.com/nodejs/node/blob/master/lib/child_process.js#L470
      env.EGG_SERVER_ENV = argv.env;
    }

    const command = argv.node || 'node';

    const options = {
      execArgv,
      env,
      stdio: 'inherit',
      detached: false,
    };

    this.logger.info('Starting %s application at %s', this.frameworkName, baseDir);

    // remove unused properties from stringify, alias had been remove by `removeAlias`
    const ignoreKeys = [ '_', '$0', 'env', 'daemon', 'stdout', 'stderr', 'timeout', 'ignore-stderr', 'node' ];
    const clusterOptions = stringify(argv, ignoreKeys);
    // Note: `spawn` is not like `fork`, had to pass `execArgv` youself
    // this.serverBin 是../start-cluster文件。将被执行。
    // clusterOptions有些属性,如title,framework,basedir
    const eggArgs = [ ...(execArgv || []), this.serverBin, clusterOptions, `--title=${argv.title}` ];
    this.logger.info('Run node %s', eggArgs.join(' '));

    // whether run in the background.
    if (isDaemon) {
      this.logger.info(`Save log file to ${logDir}`);
      const [ stdout, stderr ] = yield [ getRotatelog(argv.stdout), getRotatelog(argv.stderr) ];
      options.stdio = [ 'ignore', stdout, stderr, 'ipc' ];
      options.detached = true;

      debug('Run spawn `%s %s`', command, eggArgs.join(' '));
      // command:node
      // eggArgs: ../start-cluster脚本地址,clusterOptions包含framework参数
      // options: startcluster的参数。
      // exec和spawn有区别。
      const child = this.child = spawn(command, eggArgs, options);
      this.isReady = false;
      child.on('message', msg => {
        /* istanbul ignore else */
        if (msg && msg.action === 'egg-ready') {
          this.isReady = true;
          this.logger.info('%s started on %s', this.frameworkName, msg.data.address);
          // 父进程默认会等待子进程退出,可以通过此方法,让父进程独立于子进程
          // 退出。当然前提是,ipc通道也要关闭。即disconnect
          child.unref();
          // 终止和子进程的ipc通道,父进程退出,子进程运行。即后台运行。
          child.disconnect();
          this.exit(0);
        }
      });

      // check start status
      yield this.checkStatus(argv);
    } else {
      // start 如果没有 daemon参数
      options.stdio = [ 'inherit', 'inherit', 'inherit', 'ipc' ];
      debug('Run spawn `%s %s`', command, eggArgs.join(' '));
      const child = this.child = spawn(command, eggArgs, options);
      child.once('exit', code => {
        if (code !== 0) {
          child.emit('error', new Error(`spawn ${command} ${eggArgs.join(' ')} fail, exit code: ${code}`));
        }
      });

      // attach master signal to child
      let signal;
      [ 'SIGINT', 'SIGQUIT', 'SIGTERM' ].forEach(event => {
        process.once(event, () => {
          signal = event;
          this.exit(0);
        });
      });
      // 父进程退出,kill掉子进程。此非后台运行。
      process.once('exit', () => {
        debug('Kill child %s with %s', child.pid, signal);
        child.kill(signal);
      });
    }
  }