264 lines
9.3 KiB
JavaScript
264 lines
9.3 KiB
JavaScript
var Fiber = require("fibers");
|
|
var fs = require("fs");
|
|
var path = require("path");
|
|
var Future = require("fibers/future");
|
|
var _ = require('underscore');
|
|
var sourcemap_support = require('source-map-support');
|
|
|
|
var bootUtils = require('./boot-utils.js');
|
|
var files = require('./mini-files.js');
|
|
|
|
// This code is duplicated in tools/main.js.
|
|
var MIN_NODE_VERSION = 'v0.10.36';
|
|
|
|
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__ = {
|
|
startupHooks: [],
|
|
serverDir: serverDir,
|
|
configJson: configJson };
|
|
__meteor_runtime_config__ = { meteorRelease: configJson.meteorRelease };
|
|
|
|
|
|
// 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-app.js).
|
|
if (!process.env.NODE_ENV)
|
|
process.env.NODE_ENV = 'production';
|
|
|
|
// Map from load path to its source map.
|
|
var parsedSourceMaps = {};
|
|
|
|
// Read all the source maps into memory once.
|
|
_.each(serverJson.load, function (fileInfo) {
|
|
if (fileInfo.sourceMap) {
|
|
var rawSourceMap = fs.readFileSync(
|
|
path.resolve(serverDir, fileInfo.sourceMap), 'utf8');
|
|
// Parse the source map only once, not each time it's needed. Also remove
|
|
// the anti-XSSI header if it's there.
|
|
var parsedSourceMap = JSON.parse(rawSourceMap.replace(/^\)\]\}'/, ''));
|
|
// source-map-support doesn't ever look at the sourcesContent field, so
|
|
// there's no point in keeping it in memory.
|
|
delete parsedSourceMap.sourcesContent;
|
|
var url;
|
|
if (fileInfo.sourceMapRoot) {
|
|
// Add the specified root to any root that may be in the file.
|
|
parsedSourceMap.sourceRoot = path.join(
|
|
fileInfo.sourceMapRoot, parsedSourceMap.sourceRoot || '');
|
|
}
|
|
parsedSourceMaps[path.resolve(__dirname, fileInfo.path)] = parsedSourceMap;
|
|
}
|
|
});
|
|
|
|
var retrieveSourceMap = function (pathForSourceMap) {
|
|
if (_.has(parsedSourceMaps, pathForSourceMap))
|
|
return { map: parsedSourceMaps[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
|
|
});
|
|
|
|
// Only enabled by default in development.
|
|
if (process.env.METEOR_SHELL_DIR) {
|
|
require('./shell-server.js').listen(process.env.METEOR_SHELL_DIR);
|
|
}
|
|
|
|
// As a replacement to the old keepalives mechanism, check for a running
|
|
// parent every few seconds. Exit if the parent is not running.
|
|
//
|
|
// Two caveats to this strategy:
|
|
// * Doesn't catch the case where the parent is CPU-hogging (but maybe we
|
|
// don't want to catch that case anyway, since the bundler not yielding
|
|
// is what caused #2536).
|
|
// * Could be fooled by pid re-use, i.e. if another process comes up and
|
|
// takes the parent process's place before the child process dies.
|
|
var startCheckForLiveParent = function (parentPid) {
|
|
if (parentPid) {
|
|
if (! bootUtils.validPid(parentPid)) {
|
|
console.error("METEOR_PARENT_PID must be a valid process ID.");
|
|
process.exit(1);
|
|
}
|
|
|
|
setInterval(function () {
|
|
try {
|
|
process.kill(parentPid, 0);
|
|
} catch (err) {
|
|
console.error("Parent process is dead! Exiting.");
|
|
process.exit(1);
|
|
}
|
|
}, 3000);
|
|
}
|
|
};
|
|
|
|
|
|
Fiber(function () {
|
|
_.each(serverJson.load, function (fileInfo) {
|
|
var code = fs.readFileSync(path.resolve(serverDir, fileInfo.path));
|
|
|
|
var Npm = {
|
|
/**
|
|
* @summary Require a package that was specified using
|
|
* `Npm.depends()`.
|
|
* @param {String} name The name of the package to require.
|
|
* @locus Server
|
|
* @memberOf Npm
|
|
*/
|
|
require: function (name) {
|
|
if (! fileInfo.node_modules) {
|
|
return require(name);
|
|
}
|
|
|
|
var nodeModuleBase = path.resolve(serverDir,
|
|
files.convertToOSPath(fileInfo.node_modules));
|
|
var nodeModuleDir = path.resolve(nodeModuleBase, name);
|
|
|
|
// If the user does `Npm.require('foo/bar')`, then we should resolve to
|
|
// the package's node modules if `foo` was one of the modules we
|
|
// installed. (`foo/bar` might be implemented as `foo/bar.js` so we
|
|
// can't just naively see if all of nodeModuleDir exists.
|
|
if (fs.existsSync(path.resolve(nodeModuleBase, name.split("/")[0]))) {
|
|
return require(nodeModuleDir);
|
|
}
|
|
|
|
try {
|
|
return require(name);
|
|
} catch (e) {
|
|
// Try to guess the package name so we can print a nice
|
|
// error message
|
|
// fileInfo.path is a standard path, use files.pathSep
|
|
var filePathParts = fileInfo.path.split(files.pathSep);
|
|
var packageName = filePathParts[1].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 getAsset = function (assetPath, encoding, callback) {
|
|
var fut;
|
|
if (! callback) {
|
|
fut = new Future();
|
|
callback = fut.resolver();
|
|
}
|
|
// This assumes that we've already loaded the meteor package, so meteor
|
|
// itself (and weird special cases like js-analyze) can't call
|
|
// Assets.get*. (We could change this function so that it doesn't call
|
|
// bindEnvironment if you don't pass a callback if we need to.)
|
|
var _callback = Package.meteor.Meteor.bindEnvironment(function (err, result) {
|
|
if (result && ! encoding)
|
|
// Sadly, this copies in Node 0.10.
|
|
result = new Uint8Array(result);
|
|
callback(err, result);
|
|
}, function (e) {
|
|
console.log("Exception in callback of getAsset", e.stack);
|
|
});
|
|
|
|
// Convert a DOS-style path to Unix-style in case the application code was
|
|
// written on Windows.
|
|
assetPath = files.convertToStandardPath(assetPath);
|
|
|
|
if (!fileInfo.assets || !_.has(fileInfo.assets, assetPath)) {
|
|
_callback(new Error("Unknown asset: " + assetPath));
|
|
} else {
|
|
var filePath = path.join(serverDir, fileInfo.assets[assetPath]);
|
|
fs.readFile(files.convertToOSPath(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})";
|
|
|
|
// It is safer to use the absolute path when source map is present as
|
|
// different tooling, such as node-inspector, can get confused on relative
|
|
// urls.
|
|
|
|
// fileInfo.path is a standard path, convert it to OS path to join with
|
|
// __dirname
|
|
var fileInfoOSPath = files.convertToOSPath(fileInfo.path);
|
|
var absoluteFilePath = path.resolve(__dirname, fileInfoOSPath);
|
|
|
|
var scriptPath =
|
|
parsedSourceMaps[absoluteFilePath] ? absoluteFilePath : fileInfoOSPath;
|
|
// The final 'true' is an undocumented argument to runIn[Foo]Context that
|
|
// causes it to print out a descriptive error message on parse error. It's
|
|
// what require() uses to generate its errors.
|
|
var func = require('vm').runInThisContext(wrapped, scriptPath, true);
|
|
func.call(global, Npm, Assets); // Coffeescript
|
|
});
|
|
|
|
// run the user startup hooks. other calls to startup() during this can still
|
|
// add hooks to the end.
|
|
while (__meteor_bootstrap__.startupHooks.length) {
|
|
var hook = __meteor_bootstrap__.startupHooks.shift();
|
|
hook();
|
|
}
|
|
// Setting this to null tells Meteor.startup to call hooks immediately.
|
|
__meteor_bootstrap__.startupHooks = null;
|
|
|
|
// 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;
|
|
}
|
|
typeof Package !== 'undefined' && _.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);
|
|
|
|
if (process.env.METEOR_PARENT_PID) {
|
|
startCheckForLiveParent(process.env.METEOR_PARENT_PID);
|
|
}
|
|
}).run();
|