react-native/packager/packager.js
Justin Carmony b6503ba431 Packager status page & build validating against it.
Summary:
Creating a packager status page so React can validate a proper packager instance is running on 8081.

See #257 for details on this bug.

The biggest thing in this PR is I have it perform an exit 2 in the build script if the check fails. This will cause the build to fail, they can click on the error and see a nice message. Not sure if there is a way to throw a warning instead.

Also, I broke the bash script into several lines, in the Xcode editor it looks fine but in the source code it looks less than ideal. We might want to break that out into it's own bash script that is called. Let me know if you want to do that.
Closes https://github.com/facebook/react-native/pull/308
Github Author: Justin Carmony <justin@justincarmony.com>

Test Plan: Imported from GitHub, without a `Test Plan:` line.
2015-03-31 23:01:04 -08:00

195 lines
5.6 KiB
JavaScript

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
'use strict';
var fs = require('fs');
var path = require('path');
if (!fs.existsSync(path.resolve(__dirname, '..', 'node_modules'))) {
console.log(
'\n' +
'Could not find dependencies.\n' +
'Ensure dependencies are installed - ' +
'run \'npm install\' from project root.\n'
);
process.exit();
}
var exec = require('child_process').exec;
var ReactPackager = require('./react-packager');
var blacklist = require('./blacklist.js');
var connect = require('connect');
var http = require('http');
var launchEditor = require('./launchEditor.js');
var parseCommandLine = require('./parseCommandLine.js');
var webSocketProxy = require('./webSocketProxy.js');
var options = parseCommandLine([{
command: 'port',
default: 8081,
}, {
command: 'root',
description: 'add another root(s) to be used by the packager in this project',
}, {
command: 'assetRoots',
description: 'specify the root directories of app assets'
}]);
if (options.projectRoots) {
if (!Array.isArray(options.projectRoots)) {
options.projectRoots = options.projectRoots.split(',');
}
} else {
if (__dirname.match(/node_modules\/react-native\/packager$/)) {
// packager is running from node_modules of another project
options.projectRoots = [path.resolve(__dirname, '../../..')];
} else {
options.projectRoots = [path.resolve(__dirname, '..')];
}
}
if (options.root) {
if (typeof options.root === 'string') {
options.projectRoots.push(path.resolve(options.root));
} else {
options.root.forEach(function(root) {
options.projectRoots.push(path.resolve(root));
});
}
}
if (options.assetRoots) {
if (!Array.isArray(options.assetRoots)) {
options.assetRoots = options.assetRoots.split(',');
}
} else {
if (__dirname.match(/node_modules\/react-native\/packager$/)) {
options.assetRoots = [path.resolve(__dirname, '../../..')];
} else {
options.assetRoots = [path.resolve(__dirname, '..')];
}
}
console.log('\n' +
' ===============================================================\n' +
' | Running packager on port ' + options.port + '. \n' +
' | Keep this packager running while developing on any JS \n' +
' | projects. Feel free to close this tab and run your own \n' +
' | packager instance if you prefer. \n' +
' | \n' +
' | https://github.com/facebook/react-native \n' +
' | \n' +
' ===============================================================\n'
);
console.log('Looking for JS files in\n ', options.projectRoots.join('\n '));
process.on('uncaughtException', function(e) {
console.error(e);
console.error(e.stack);
console.error('\n >>> ERROR: could not create packager - please shut down ' +
'any existing instances that are already running.\n\n');
});
var server = runServer(options, function() {
console.log('\nReact packager ready.\n');
});
webSocketProxy.attachToServer(server, '/debugger-proxy');
function loadRawBody(req, res, next) {
req.rawBody = '';
req.setEncoding('utf8');
req.on('data', function(chunk) {
req.rawBody += chunk;
});
req.on('end', function() {
next();
});
}
function openStackFrameInEditor(req, res, next) {
if (req.url === '/open-stack-frame') {
var frame = JSON.parse(req.rawBody);
launchEditor(frame.file, frame.lineNumber);
res.end('OK');
} else {
next();
}
}
function getDevToolsLauncher(options) {
return function(req, res, next) {
if (req.url === '/debugger-ui') {
var debuggerPath = path.join(__dirname, 'debugger.html');
res.writeHead(200, {'Content-Type': 'text/html'});
fs.createReadStream(debuggerPath).pipe(res);
} else if (req.url === '/launch-chrome-devtools') {
var debuggerURL = 'http://localhost:' + options.port + '/debugger-ui';
var script = 'launchChromeDevTools.applescript';
console.log('Launching Dev Tools...');
exec(path.join(__dirname, script) + ' ' + debuggerURL, function(err, stdout, stderr) {
if (err) {
console.log('Failed to run ' + script, err);
}
console.log(stdout);
console.warn(stderr);
});
res.end('OK');
} else {
next();
}
};
}
// A status page so the React/project.pbxproj build script
// can verify that packager is running on 8081 and not
// another program / service.
function statusPageMiddleware(req, res, next) {
if (req.url === '/status') {
res.end('packager-status:running');
} else {
next();
}
}
function getAppMiddleware(options) {
return ReactPackager.middleware({
projectRoots: options.projectRoots,
blacklistRE: blacklist(false),
cacheVersion: '2',
transformModulePath: require.resolve('./transformer.js'),
assetRoots: options.assetRoots,
});
}
function runServer(
options, /* {[]string projectRoot, bool web} */
readyCallback
) {
var app = connect()
.use(loadRawBody)
.use(openStackFrameInEditor)
.use(getDevToolsLauncher(options))
.use(statusPageMiddleware)
.use(getAppMiddleware(options));
options.projectRoots.forEach(function(root) {
app.use(connect.static(root));
});
app.use(connect.logger())
.use(connect.compress())
.use(connect.errorHandler());
return http.createServer(app).listen(options.port, readyCallback);
}