Created
September 2, 2015 03:42
-
-
Save fiveisprime/711b5bbb7fa661a0cdf0 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment