UNPKG

taro-s4s

Version:

Nerv-multi多端开发解决方案

308 lines (271 loc) 9.26 kB
const { PackagerLogsStream, Project, ProjectSettings, ProjectUtils } = require('xdl') const _ = require('lodash') const spawn = require('cross-spawn') const ProgressBar = require('progress') const bunyan = require('@expo/bunyan') const chalk = require('chalk') const log = require('./log') function installExitHooks (projectDir, isInteractive) { if (!isInteractive && process.platform === 'win32') { require('readline') .createInterface({ input: process.stdin, output: process.stdout }) .on('SIGINT', () => { process.emit('SIGINT') }) } process.on('SIGINT', () => { log.withTimestamp('Stopping packager...') cleanUpPackager(projectDir).then(() => { // TODO: this shows up after process exits, fix it log.withTimestamp(chalk.green('Packager stopped.')) process.exit() }) }) } async function cleanUpPackager (projectDir) { const result = await Promise.race([ Project.stopAsync(projectDir), new Promise((resolve, reject) => setTimeout(resolve, 1000, 'stopFailed')) ]) if (result === 'stopFailed') { // find RN packager pid, attempt to kill manually try { const { packagerPid } = await ProjectSettings.readPackagerInfoAsync(projectDir) process.kill(packagerPid) } catch (e) { process.exit(1) } } } function shouldIgnoreMsg (msg) { return msg.indexOf('Duplicate module name: bser') >= 0 || msg.indexOf('Duplicate module name: fb-watchman') >= 0 || msg.indexOf('Warning: React.createClass is no longer supported') >= 0 || msg.indexOf('Warning: PropTypes has been moved to a separate package') >= 0 } function run (projectDir, onReady, options, isInteractive = false) { let packagerReady = false let needsClear = false let logBuffer = '' let progressBar if (process.platform !== 'win32') { const watchmanExists = spawn.sync('which', ['watchman']).status === 0 if (process.platform === 'darwin' && !watchmanExists) { const watcherDetails = spawn.sync('sysctl', ['kern.maxfiles']).stdout.toString() if (parseInt(watcherDetails.split(':')[1].trim()) < 5242880) { log.withTimestamp( `${chalk.red(`Unable to start server`)} See https://git.io/v5vcn for more information, either install watchman or run the following snippet: ${chalk.cyan(` sudo sysctl -w kern.maxfiles=5242880 sudo sysctl -w kern.maxfilesperproc=524288`)} ` ) process.exit(1) } } else if (!watchmanExists) { try { const watcherDetails = spawn .sync('sysctl', ['fs.inotify.max_user_watches']) .stdout.toString() if (parseInt(watcherDetails.split('=')[1].trim()) < 12288) { log.withTimestamp( `${chalk.red(`Unable to start server`)} See https://git.io/v5vcn for more information, either install watchman or run the following snippet: ${chalk.cyan(` sudo sysctl -w fs.inotify.max_user_instances=1024 sudo sysctl -w fs.inotify.max_user_watches=12288`)}` ) process.exit(1) } } catch (e) { // note(brentvatne): I'm not sure why stdout is null for some OS's // https://github.com/react-community/create-react-native-app/issues/391 log.withTimestamp( 'Warning: Unable to run `sysctl fs.inotify.max_user_watches`. If you encounter issues, please refer to https://git.io/v5vcn' ) } } } const handleLogChunk = chunk => { // pig, meet lipstick // 1. https://github.com/facebook/react-native/issues/14620 // 2. https://github.com/facebook/react-native/issues/14610 // 3. https://github.com/react-community/create-react-native-app/issues/229#issuecomment-308654303 // @ide is investigating 3), the first two are upstream issues that will // likely be resolved by others if (shouldIgnoreMsg(chunk.msg)) { return } // we don't need to print the entire manifest when loading the app if (chunk.msg.indexOf(' with appParams: ') >= 0) { if (needsClear) { // this is set when we previously encountered an error // TODO clearConsole() } let devEnabled = chunk.msg.includes('__DEV__ === true') log.withTimestamp( `Running app on ${chunk.deviceName} in ${devEnabled ? 'development' : 'production'} mode\n` ) return } if (chunk.msg === 'Dependency graph loaded.') { packagerReady = true onReady() return } if (packagerReady) { const message = `${chunk.msg.trim()}\n` logWithLevel(chunk) if (chunk.level === bunyan.ERROR) { // if you run into a syntax error then we should clear log output on reload needsClear = message.indexOf('SyntaxError') >= 0 } } else { if (chunk.level >= bunyan.ERROR) { log(chalk.yellow('***ERROR STARTING PACKAGER***')) log(logBuffer) log(chalk.red(chunk.msg)) logBuffer = '' } else { logBuffer += chunk.msg + '\n' } } } // Subscribe to packager/server logs new PackagerLogsStream({ // eslint-disable-line no-new projectRoot: projectDir, onStartBuildBundle: () => { progressBar = new ProgressBar('Building JavaScript bundle [:bar] :percent', { total: 100, clear: true, complete: '=', incomplete: ' ' }) log.setBundleProgressBar(progressBar) }, onProgressBuildBundle: percent => { if (!progressBar || progressBar.complete) return let ticks = percent - progressBar.curr ticks > 0 && progressBar.tick(ticks) }, onFinishBuildBundle: (err, startTime, endTime) => { if (progressBar && !progressBar.complete) { progressBar.tick(100 - progressBar.curr) } if (progressBar) { log.setBundleProgressBar(null) progressBar = null console.log(err) if (err) { log.withTimestamp(chalk.red(`Failed building JavaScript bundle`)) } else { let duration = endTime - startTime log.withTimestamp(chalk.green(`Finished building JavaScript bundle in ${duration}ms`)) } } }, updateLogs: updater => { let newLogChunks = updater([]) if (progressBar) { // Restarting watchman causes `onFinishBuildBundle` to not fire. Until // this is handled upstream in xdl, reset progress bar with error here. newLogChunks.forEach(chunk => { if (chunk.msg === 'Restarted watchman.') { progressBar.tick(100 - progressBar.curr) log.setBundleProgressBar(null) progressBar = null log.withTimestamp(chalk.red('Failed building JavaScript bundle')) } }) } newLogChunks.map(handleLogChunk) } }) // Subscribe to device updates separately from packager/server updates ProjectUtils.attachLoggerStream(projectDir, { stream: { write: chunk => { if (chunk.tag === 'device') { handleLogChunk(chunk) } } }, type: 'raw' }) installExitHooks(projectDir, isInteractive) log.withTimestamp('Starting packager...') Project.startAsync(projectDir, options).then( () => {}, reason => { log.withTimestamp(chalk.red(`Error starting packager: ${reason.stack}`)) process.exit(1) } ) } const logStackTrace = (chunk, logFn, nestedLogFn, colorFn) => { let traceInfo try { traceInfo = JSON.parse(chunk.msg) } catch (e) { return logFn(colorFn(chunk.msg)) } let { message, stack } = traceInfo logFn(colorFn(chalk.bold(message))) const isLibraryFrame = line => { return line.startsWith('node_modules') } let stackFrames = _.compact(stack.split('\n')) let lastAppCodeFrameIndex = _.findLastIndex(stackFrames, line => { return !isLibraryFrame(line) }) let lastFrameIndexToLog = Math.min( stackFrames.length - 1, lastAppCodeFrameIndex + 2 // show max two more frames after last app code frame ) let unloggedFrames = stackFrames.length - lastFrameIndexToLog // If we're only going to exclude one frame, just log them all if (unloggedFrames === 1) { lastFrameIndexToLog = stackFrames.length - 1 unloggedFrames = 0 } for (let i = 0; i <= lastFrameIndexToLog; i++) { let line = stackFrames[i] if (!line) { continue } else if (line.match(/react-native\/.*YellowBox.js/)) { continue } if (line.startsWith('node_modules')) { nestedLogFn(colorFn('- ' + line)) } else { nestedLogFn(colorFn('* ' + line)) } } if (unloggedFrames > 0) { nestedLogFn(colorFn(`- ... ${unloggedFrames} more stack frames from framework internals`)) } } const logWithLevel = chunk => { if (!chunk.msg) { return } let colorFn = str => str if (chunk.level === bunyan.WARN) { colorFn = chalk.yellow } else if (chunk.level === bunyan.ERROR) { colorFn = chalk.red } if (chunk.includesStack) { logStackTrace(chunk, log.withTimestamp, log, colorFn) } else { logLines(chunk.msg, log.withTimestamp, colorFn) } } const logLines = (msg, logFn, colorFn) => { for (let line of msg.split('\n')) { logFn(colorFn(line)) } } module.exports = { run }