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); }); } }