Files
meteor/tools/server/boot.js
David Glasser 7456124102 Node stack traces are now rewritten based on (slightly wrong) source maps.
Also, assume that the program.json given to boot.js is relative to CWD, not
source file.
2013-07-12 10:11:29 -07:00

166 lines
5.4 KiB
JavaScript

var Fiber = require("fibers");
var fs = require("fs");
var path = require("path");
var Future = require(path.join("fibers", "future"));
var _ = require('underscore');
var sourcemap_support = require('source-map-support');
// This code is duplicated in tools/server/server.js.
var MIN_NODE_VERSION = 'v0.8.24';
if (require('semver').lt(process.version, MIN_NODE_VERSION)) {
process.stderr.write(
'Meteor requires Node ' + MIN_NODE_VERSION + ' or later.\n');
process.exit(1);
}
// read our control files
var serverJsonPath = path.resolve(process.argv[2]);
var serverDir = path.dirname(serverJsonPath);
var serverJson = JSON.parse(fs.readFileSync(serverJsonPath, 'utf8'));
var configJson =
JSON.parse(fs.readFileSync(path.resolve(serverDir, 'config.json'), 'utf8'));
// Set up environment
__meteor_bootstrap__ = {
startup_hooks: [],
serverDir: serverDir,
configJson: configJson };
__meteor_runtime_config__ = { meteorRelease: configJson.release };
// connect (and some other NPM modules) use $NODE_ENV to make some decisions;
// eg, if $NODE_ENV is not production, they send stack traces on error. connect
// considers 'development' to be the default mode, but that's less safe than
// assuming 'production' to be the default. If you really want development mode,
// set it in your wrapper script (eg, run.js).
if (!process.env.NODE_ENV)
process.env.NODE_ENV = 'production';
// Map from load path to its source map.
var sourceMapsAsStrings = {};
// Read all the source maps into memory once.
_.each(serverJson.load, function (fileInfo) {
if (fileInfo.sourceMap) {
sourceMapsAsStrings[fileInfo.path] = fs.readFileSync(
path.resolve(serverDir, fileInfo.sourceMap), 'utf8');
}
});
var retrieveSourceMap = function (pathForSourceMap) {
if (_.has(sourceMapsAsStrings, pathForSourceMap))
return {data: sourceMapsAsStrings[pathForSourceMap]};
return null;
};
sourcemap_support.install({
// Use the source maps specified in program.json instead of parsing source
// code for them.
retrieveSourceMap: retrieveSourceMap,
// For now, don't fix the source line in uncaught exceptions, because we
// haven't fixed handleUncaughtExceptions in source-map-support to properly
// locate the source files.
handleUncaughtExceptions: false
});
Fiber(function () {
_.each(serverJson.load, function (fileInfo) {
var code = fs.readFileSync(path.resolve(serverDir, fileInfo.path));
var Npm = {
require: function (name) {
if (! fileInfo.node_modules) {
return require(name);
}
var nodeModuleDir =
path.resolve(serverDir, fileInfo.node_modules, name);
if (fs.existsSync(nodeModuleDir)) {
return require(nodeModuleDir);
}
try {
return require(name);
} catch (e) {
// Try to guess the package name so we can print a nice
// error message
var filePathParts = fileInfo.path.split(path.sep);
var packageName = filePathParts[2].replace(/\.js$/, '');
// XXX better message
throw new Error(
"Can't find npm module '" + name +
"'. Did you forget to call 'Npm.depends' in package.js " +
"within the '" + packageName + "' package?");
}
}
};
var staticDirectory = path.resolve(serverDir, fileInfo.staticDirectory);
var getAsset = function (assetPath, encoding, callback) {
var fut;
if (! callback) {
fut = new Future();
callback = fut.resolver();
}
var _callback = Meteor.bindEnvironment(function (err, result) {
if (result && ! encoding)
// Sadly, this copies in Node 0.10.
result = new Uint8Array(result);
callback(err, result);
}, function (e) {
Meteor._debug("Exception in callback of getAsset", e.stack);
});
var filePath = path.join(staticDirectory, assetPath);
if (filePath.indexOf("..") !== -1)
throw new Error(".. is not allowed in asset paths.");
fs.readFile(filePath, encoding, _callback);
if (fut)
return fut.wait();
};
var Assets = {
getText: function (assetPath, callback) {
return getAsset(assetPath, "utf8", callback);
},
getBinary: function (assetPath, callback) {
return getAsset(assetPath, undefined, callback);
}
};
// \n is necessary in case final line is a //-comment
var wrapped = "(function(Npm, Assets){" + code + "\n})";
var func = require('vm').runInThisContext(wrapped, fileInfo.path, true);
func.call(global, Npm, Assets); // Coffeescript
});
// run the user startup hooks.
_.each(__meteor_bootstrap__.startup_hooks, function (x) { x(); });
// find and run main()
// XXX hack. we should know the package that contains main.
var mains = [];
var globalMain;
if ('main' in global) {
mains.push(main);
globalMain = main;
}
_.each(Package, function (p, n) {
if ('main' in p && p.main !== globalMain) {
mains.push(p.main);
}
});
if (! mains.length) {
process.stderr.write("Program has no main() function.\n");
process.exit(1);
}
if (mains.length > 1) {
process.stderr.write("Program has more than one main() function?\n");
process.exit(1);
}
var exitCode = mains[0].call({}, process.argv.slice(3));
// XXX hack, needs a better way to keep alive
if (exitCode !== 'DAEMON')
process.exit(exitCode);
}).run();