mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Factor webapp-specific stuff (and keepalive) out of boot.js into webapp package.
Replace type: "bare" with type: "server" in attributes.json. This now just means "only make one server process" rather than controlling the boot script. Programs that are type: "traditional" that want to be a webapp now need to explicitly depend on the webapp package.
This commit is contained in:
@@ -4,6 +4,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('accounts-base', ['client', 'server']);
|
||||
api.use('routepolicy', 'server');
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('reload', 'client');
|
||||
api.use('routepolicy', 'server');
|
||||
api.use('startup', 'client');
|
||||
|
||||
@@ -3,6 +3,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('underscore', 'server');
|
||||
// make sure we come after livedata, so we load after the sockjs
|
||||
// server has been instantiated.
|
||||
|
||||
@@ -10,6 +10,7 @@ Package.on_use(function (api) {
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('underscore');
|
||||
api.use('random');
|
||||
api.use('jquery', 'client');
|
||||
|
||||
@@ -10,6 +10,11 @@ Package.on_use(function (api) {
|
||||
api.use(['check', 'random', 'ejson', 'json', 'underscore', 'deps', 'logging'],
|
||||
['client', 'server']);
|
||||
|
||||
// XXX we do NOT require webapp here, because it's OK to use this package on a
|
||||
// server architecture without making a server (in order to do
|
||||
// server-to-server DDP as a client). So if you want to provide a DDP server,
|
||||
// you need to use webapp before you use livedata.
|
||||
|
||||
// Transport
|
||||
api.use('reload', 'client');
|
||||
api.use('routepolicy', 'server');
|
||||
|
||||
@@ -22,18 +22,14 @@ _.extend(Meteor._RemoteCollectionDriver.prototype, {
|
||||
// Create the singleton _RemoteCollectionDriver only on demand, so we
|
||||
// only require Mongo configuration if it's actually used (eg, not if
|
||||
// you're only trying to receive data from a remote DDP server.)
|
||||
var theDriver = null;
|
||||
Meteor._getRemoteCollectionDriver = function () {
|
||||
if (! theDriver) {
|
||||
// XXX kind of hacky
|
||||
var mongoUrl = Meteor._get(__meteor_bootstrap__.deployConfig,
|
||||
'packages', 'mongo-livedata', 'url');
|
||||
// XXX bad error since it could also be set directly in METEOR_DEPLOY_CONFIG
|
||||
if (! mongoUrl)
|
||||
throw new Error("MONGO_URL must be set in environment");
|
||||
Meteor._getRemoteCollectionDriver = _.once(function () {
|
||||
// XXX kind of hacky
|
||||
var mongoUrl = (typeof __meteor_bootstrap__ !== 'undefined' &&
|
||||
Meteor._get(__meteor_bootstrap__.deployConfig,
|
||||
'packages', 'mongo-livedata', 'url'));
|
||||
// XXX bad error since it could also be set directly in METEOR_DEPLOY_CONFIG
|
||||
if (! mongoUrl)
|
||||
throw new Error("MONGO_URL must be set in environment");
|
||||
|
||||
theDriver = new Meteor._RemoteCollectionDriver(mongoUrl);
|
||||
}
|
||||
|
||||
return theDriver;
|
||||
};
|
||||
return new Meteor._RemoteCollectionDriver(mongoUrl);
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use('underscore', 'server');
|
||||
api.add_files('routepolicy.js', 'server');
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ Package.on_use(function (api) {
|
||||
});
|
||||
|
||||
Package.on_test(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use(['tinytest', 'underscore', 'liverange', 'deps', 'domutils',
|
||||
'minimongo', 'random']);
|
||||
api.use(['spark', 'test-helpers'], 'client');
|
||||
|
||||
@@ -3,6 +3,7 @@ Package.describe({
|
||||
});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use('webapp', 'server');
|
||||
api.use(['templating'], 'client');
|
||||
api.use(['underscore'], ['client', 'server']);
|
||||
|
||||
|
||||
1
packages/webapp/.gitignore
vendored
Normal file
1
packages/webapp/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.build*
|
||||
1
packages/webapp/.npm/.gitignore
vendored
Normal file
1
packages/webapp/.npm/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
packages/webapp/.npm/README
Normal file
7
packages/webapp/.npm/README
Normal file
@@ -0,0 +1,7 @@
|
||||
This directory and the files immediately inside it are automatically generated
|
||||
when you change this package's NPM dependencies. Commit the files in this
|
||||
directory (npm-shrinkwrap.json, .gitignore, and this README) to source control
|
||||
so that others run the same versions of sub-dependencies.
|
||||
|
||||
You should NOT check in the node_modules directory that Meteor automatically
|
||||
creates; if you are using git, the .gitignore file tells git to ignore it.
|
||||
41
packages/webapp/.npm/npm-shrinkwrap.json
generated
Normal file
41
packages/webapp/.npm/npm-shrinkwrap.json
generated
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"connect": {
|
||||
"version": "1.9.2",
|
||||
"from": "connect@1.9.2",
|
||||
"resolved": "https://registry.npmjs.org/connect/-/connect-1.9.2.tgz",
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "0.6.4",
|
||||
"from": "qs@>= 0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-0.6.4.tgz"
|
||||
},
|
||||
"mime": {
|
||||
"version": "1.2.9",
|
||||
"from": "mime@>= 0.0.1"
|
||||
},
|
||||
"formidable": {
|
||||
"version": "1.0.14",
|
||||
"from": "formidable@1.0.x",
|
||||
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.0.14.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"gzippo": {
|
||||
"version": "0.1.7",
|
||||
"from": "https://github.com/meteor/gzippo/tarball/1e4b955439abc643879ae264b28a761521818f3b",
|
||||
"resolved": "https://github.com/meteor/gzippo/tarball/1e4b955439abc643879ae264b28a761521818f3b",
|
||||
"dependencies": {
|
||||
"mime": {
|
||||
"version": "1.2.9",
|
||||
"from": "mime@>= 1.2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"useragent": {
|
||||
"version": "2.0.1",
|
||||
"from": "useragent@2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/useragent/-/useragent-2.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
15
packages/webapp/package.js
Normal file
15
packages/webapp/package.js
Normal file
@@ -0,0 +1,15 @@
|
||||
Package.describe({
|
||||
summary: "Serves a Meteor app over HTTP",
|
||||
internal: true
|
||||
});
|
||||
|
||||
Npm.depends({connect: "1.9.2",
|
||||
// allow clientMaxAge to be set to 0:
|
||||
// https://github.com/tomgco/gzippo/pull/49
|
||||
gzippo: "https://github.com/meteor/gzippo/tarball/1e4b955439abc643879ae264b28a761521818f3b",
|
||||
useragent: "2.0.1"});
|
||||
|
||||
Package.on_use(function (api) {
|
||||
api.use(['underscore'], 'server');
|
||||
api.add_files('webapp_server.js', 'server');
|
||||
});
|
||||
332
packages/webapp/webapp_server.js
Normal file
332
packages/webapp/webapp_server.js
Normal file
@@ -0,0 +1,332 @@
|
||||
////////// Requires //////////
|
||||
|
||||
var fs = Npm.require("fs");
|
||||
var os = Npm.require("os");
|
||||
var path = Npm.require("path");
|
||||
var url = Npm.require("url");
|
||||
|
||||
var connect = Npm.require('connect');
|
||||
var gzippo = Npm.require('gzippo');
|
||||
var optimist = Npm.require('optimist');
|
||||
var useragent = Npm.require('useragent');
|
||||
|
||||
// Keepalives so that when the outer server dies unceremoniously and
|
||||
// doesn't kill us, we quit ourselves. A little gross, but better than
|
||||
// pidfiles.
|
||||
// XXX This should really be part of the boot script, not the webapp package.
|
||||
// Or we should just get rid of it, and rely on containerization.
|
||||
var initKeepalive = function () {
|
||||
var keepaliveCount = 0;
|
||||
|
||||
process.stdin.on('data', function (data) {
|
||||
keepaliveCount = 0;
|
||||
});
|
||||
|
||||
process.stdin.resume();
|
||||
|
||||
setInterval(function () {
|
||||
keepaliveCount ++;
|
||||
if (keepaliveCount >= 3) {
|
||||
console.log("Failed to receive keepalive! Exiting.");
|
||||
process.exit(1);
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
|
||||
// #BrowserIdentification
|
||||
//
|
||||
// We have multiple places that want to identify the browser: the
|
||||
// unsupported browser page, the appcache package, and, eventually
|
||||
// delivering browser polyfills only as needed.
|
||||
//
|
||||
// To avoid detecting the browser in multiple places ad-hoc, we create a
|
||||
// Meteor "browser" object. It uses but does not expose the npm
|
||||
// useragent module (we could choose a different mechanism to identify
|
||||
// the browser in the future if we wanted to). The browser object
|
||||
// contains
|
||||
//
|
||||
// * `name`: the name of the browser in camel case
|
||||
// * `major`, `minor`, `patch`: integers describing the browser version
|
||||
//
|
||||
// Also here is an early version of a Meteor `request` object, intended
|
||||
// to be a high-level description of the request without exposing
|
||||
// details of connect's low-level `req`. Currently it contains:
|
||||
//
|
||||
// * `browser`: browser identification object described above
|
||||
// * `url`: parsed url, including parsed query params
|
||||
//
|
||||
// As a temporary hack there is a `categorizeRequest` function on
|
||||
// __meteor_bootstrap__ which converts a connect `req` to a Meteor
|
||||
// `request`. This can go away once smart packages such as appcache are
|
||||
// being passed a `request` object directly when they serve content.
|
||||
//
|
||||
// This allows `request` to be used uniformly: it is passed to the html
|
||||
// attributes hook, and the appcache package can use it when deciding
|
||||
// whether to generate a 404 for the manifest.
|
||||
//
|
||||
// Real routing / server side rendering will probably refactor this
|
||||
// heavily.
|
||||
|
||||
|
||||
// e.g. "Mobile Safari" => "mobileSafari"
|
||||
var camelCase = function (name) {
|
||||
var parts = name.split(' ');
|
||||
parts[0] = parts[0].toLowerCase();
|
||||
for (var i = 1; i < parts.length; ++i) {
|
||||
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1);
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
||||
|
||||
var identifyBrowser = function (req) {
|
||||
var userAgent = useragent.lookup(req.headers['user-agent']);
|
||||
return {
|
||||
name: camelCase(userAgent.family),
|
||||
major: +userAgent.major,
|
||||
minor: +userAgent.minor,
|
||||
patch: +userAgent.patch
|
||||
};
|
||||
};
|
||||
|
||||
var categorizeRequest = function (req) {
|
||||
return {
|
||||
browser: identifyBrowser(req),
|
||||
url: url.parse(req.url, true)
|
||||
};
|
||||
};
|
||||
|
||||
var htmlAttributes = function (template, request) {
|
||||
var attributes = '';
|
||||
_.each(__meteor_bootstrap__.htmlAttributeHooks || [], function (hook) {
|
||||
var attribute = hook(request);
|
||||
if (attribute !== null && attribute !== undefined && attribute !== '')
|
||||
attributes += ' ' + attribute;
|
||||
});
|
||||
return template.replace('##HTML_ATTRIBUTES##', attributes);
|
||||
};
|
||||
|
||||
// Serve app HTML for this URL?
|
||||
var appUrl = function (url) {
|
||||
if (url === '/favicon.ico' || url === '/robots.txt')
|
||||
return false;
|
||||
|
||||
// NOTE: app.manifest is not a web standard like favicon.ico and
|
||||
// robots.txt. It is a file name we have chosen to use for HTML5
|
||||
// appcache URLs. It is included here to prevent using an appcache
|
||||
// then removing it from poisoning an app permanently. Eventually,
|
||||
// once we have server side routing, this won't be needed as
|
||||
// unknown URLs with return a 404 automatically.
|
||||
if (url === '/app.manifest')
|
||||
return false;
|
||||
|
||||
// Avoid serving app HTML for declared routes such as /sockjs/.
|
||||
if (__meteor_bootstrap__._routePolicy &&
|
||||
__meteor_bootstrap__._routePolicy.classify(url))
|
||||
return false;
|
||||
|
||||
// we currently return app HTML on all URLs by default
|
||||
return true;
|
||||
};
|
||||
|
||||
var runWebAppServer = function () {
|
||||
// read the control for the client we'll be serving up
|
||||
var clientJsonPath = path.join(__meteor_bootstrap__.serverDir,
|
||||
__meteor_bootstrap__.configJson.client);
|
||||
var clientDir = path.dirname(clientJsonPath);
|
||||
var clientJson = JSON.parse(fs.readFileSync(clientJsonPath, 'utf8'));
|
||||
|
||||
if (clientJson.format !== "browser-program-pre1")
|
||||
throw new Error("Unsupported format for client assets: " +
|
||||
JSON.stringify(clientJson.format));
|
||||
|
||||
// XXX change all this config to something more reasonable
|
||||
var deployConfig =
|
||||
process.env.METEOR_DEPLOY_CONFIG
|
||||
? JSON.parse(process.env.METEOR_DEPLOY_CONFIG) : {};
|
||||
if (!deployConfig.packages)
|
||||
deployConfig.packages = {};
|
||||
if (!deployConfig.boot)
|
||||
deployConfig.boot = {};
|
||||
if (!deployConfig.boot.bind)
|
||||
deployConfig.boot.bind = {};
|
||||
|
||||
// check environment for legacy env variables.
|
||||
if (process.env.PORT && !_.has(deployConfig.boot.bind, 'localPort')) {
|
||||
deployConfig.boot.bind.localPort = parseInt(process.env.PORT);
|
||||
}
|
||||
if (process.env.MONGO_URL) {
|
||||
if (!deployConfig.packages['mongo-livedata'])
|
||||
deployConfig.packages['mongo-livedata'] = {};
|
||||
deployConfig.packages['mongo-livedata'].url = process.env.MONGO_URL;
|
||||
}
|
||||
|
||||
// webserver
|
||||
var app = connect.createServer();
|
||||
|
||||
var staticCacheablePath = path.join(clientDir, clientJson.staticCacheable);
|
||||
if (staticCacheablePath)
|
||||
// cacheable files are files that should never change. Typically
|
||||
// named by their hash (eg meteor bundled js and css files).
|
||||
// cache them ~forever (1yr)
|
||||
//
|
||||
// 'root' option is to work around an issue in connect/gzippo.
|
||||
// See https://github.com/meteor/meteor/pull/852
|
||||
app.use(gzippo.staticGzip(staticCacheablePath,
|
||||
{clientMaxAge: 1000 * 60 * 60 * 24 * 365,
|
||||
root: '/'}));
|
||||
|
||||
// cache non-cacheable file anyway. This isn't really correct, as
|
||||
// users can change the files and changes won't propogate
|
||||
// immediately. However, if we don't cache them, browsers will
|
||||
// 'flicker' when rerendering images. Eventually we will probably want
|
||||
// to rewrite URLs of static assets to include a query parameter to
|
||||
// bust caches. That way we can both get good caching behavior and
|
||||
// allow users to change assets without delay.
|
||||
// https://github.com/meteor/meteor/issues/773
|
||||
var staticPath = path.join(clientDir, clientJson.static);
|
||||
if (staticPath)
|
||||
app.use(gzippo.staticGzip(staticPath,
|
||||
{clientMaxAge: 1000 * 60 * 60 * 24,
|
||||
root: '/'}));
|
||||
|
||||
// start up app
|
||||
_.extend(__meteor_bootstrap__, {
|
||||
app: app,
|
||||
// metadata about this bundle
|
||||
// XXX this could use some refactoring to better distinguish
|
||||
// server and client
|
||||
bundle: {
|
||||
manifest: clientJson.manifest,
|
||||
root: clientDir
|
||||
},
|
||||
// function that takes a connect `req` object and returns a summary
|
||||
// object with information about the request. See
|
||||
// #BrowserIdentifcation
|
||||
categorizeRequest: categorizeRequest,
|
||||
// list of functions to be called to determine any attributes to be
|
||||
// added to the '<html>' tag. Each function is passed a 'request'
|
||||
// object (see #BrowserIdentifcation) and should return a string,
|
||||
htmlAttributeHooks: [],
|
||||
deployConfig: deployConfig
|
||||
});
|
||||
|
||||
// Let the rest of the packages (and Meteor.startup hooks) insert connect
|
||||
// middlewares and update __meteor_runtime_config__, then keep going to set up
|
||||
// actually serving HTML.
|
||||
// @export main
|
||||
main = function (argv) {
|
||||
argv = optimist(argv).boolean('keepalive').argv;
|
||||
|
||||
var boilerplateHtmlPath = path.join(clientDir, clientJson.page);
|
||||
var boilerplateHtml =
|
||||
fs.readFileSync(boilerplateHtmlPath, 'utf8').replace(
|
||||
"// ##RUNTIME_CONFIG##",
|
||||
"__meteor_runtime_config__ = " +
|
||||
JSON.stringify(__meteor_runtime_config__) + ";");
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (! appUrl(req.url))
|
||||
return next();
|
||||
|
||||
var request = categorizeRequest(req);
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
||||
|
||||
var requestSpecificHtml = htmlAttributes(boilerplateHtml, request);
|
||||
res.write(requestSpecificHtml);
|
||||
res.end();
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Return 404 by default, if no other handlers serve this URL.
|
||||
app.use(function (req, res) {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// only start listening after all the startup code has run.
|
||||
var bind = deployConfig.boot.bind;
|
||||
app.listen(bind.localPort || 0, function() {
|
||||
if (argv.keepalive)
|
||||
console.log("LISTENING"); // must match run.js
|
||||
var port = app.address().port;
|
||||
if (bind.viaProxy) {
|
||||
bindToProxy(bind.viaProxy);
|
||||
}
|
||||
});
|
||||
|
||||
if (argv.keepalive)
|
||||
initKeepalive();
|
||||
return 'DAEMON';
|
||||
};
|
||||
};
|
||||
|
||||
var bindToProxy = function (proxyConfig) {
|
||||
// XXX also support galaxy-based lookup
|
||||
if (!proxyConfig.proxyEndpoint)
|
||||
throw new Error("missing proxyEndpoint");
|
||||
if (!proxyConfig.bindHost)
|
||||
throw new Error("missing bindHost");
|
||||
// XXX move these into deployConfig?
|
||||
if (!process.env.GALAXY_JOB)
|
||||
throw new Error("missing $GALAXY_JOB");
|
||||
if (!process.env.LAST_START)
|
||||
throw new Error("missing $LAST_START");
|
||||
|
||||
// XXX rename pid argument to bindTo.
|
||||
var pid = {
|
||||
job: process.env.GALAXY_JOB,
|
||||
lastStarted: process.env.LAST_START
|
||||
};
|
||||
var myHost = os.hostname();
|
||||
|
||||
var ddpBindTo = proxyConfig.unprivilegedPorts ? {
|
||||
ddpUrl: 'ddp://' + proxyConfig.bindHost + ':4433/',
|
||||
insecurePort: 8080
|
||||
} : {
|
||||
ddpUrl: 'ddp://' + proxyConfig.bindHost + '/'
|
||||
};
|
||||
|
||||
// This is run after packages are loaded (in main) so we can use
|
||||
// Meteor.connect.
|
||||
var proxy = Meteor.connect(proxyConfig.proxyEndpoint);
|
||||
var route = process.env.ROUTE;
|
||||
var host = route.split(":")[0];
|
||||
var port = route.split(":")[1];
|
||||
proxy.call('bindDdp', {
|
||||
pid: pid,
|
||||
bindTo: ddpBindTo,
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port,
|
||||
pathPrefix: '/websocket'
|
||||
}
|
||||
});
|
||||
proxy.call('bindHttp', {
|
||||
pid: pid,
|
||||
bindTo: {
|
||||
host: proxyConfig.bindHost,
|
||||
port: proxyConfig.unprivilegedPorts ? 8080 : 80
|
||||
},
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
proxy.call('bindHttp', {
|
||||
pid: pid,
|
||||
bindTo: {
|
||||
host: proxyConfig.bindHost,
|
||||
port: proxyConfig.unprivilegedPorts ? 4433: 443,
|
||||
ssl: true
|
||||
},
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
runWebAppServer();
|
||||
|
||||
@@ -95,12 +95,10 @@ which npm
|
||||
# you update version numbers.
|
||||
|
||||
cd "$DIR/lib/node_modules"
|
||||
npm install connect@1.9.2 # not 2.x yet. sockjs doesn't work w/ new connect
|
||||
npm install optimist@0.3.5
|
||||
npm install semver@1.1.0
|
||||
npm install handlebars@1.0.7
|
||||
npm install clean-css@0.8.3
|
||||
npm install useragent@2.0.1
|
||||
npm install request@2.12.0
|
||||
npm install keypress@0.1.0
|
||||
npm install http-proxy@0.8.5
|
||||
@@ -110,10 +108,6 @@ npm install tar@0.1.14
|
||||
npm install kexec@0.1.1
|
||||
npm install shell-quote@0.0.1
|
||||
|
||||
# allow clientMaxAge to be set to 0:
|
||||
# https://github.com/tomgco/gzippo/pull/49
|
||||
npm install https://github.com/meteor/gzippo/tarball/1e4b955439
|
||||
|
||||
# uglify-js has a bug which drops 'undefined' in arrays:
|
||||
# https://github.com/mishoo/UglifyJS2/pull/97
|
||||
npm install https://github.com/meteor/UglifyJS2/tarball/aa5abd14d3
|
||||
|
||||
@@ -1036,16 +1036,12 @@ _.extend(JsImageTarget.prototype, {
|
||||
// options:
|
||||
// - clientTarget: the ClientTarget to serve up over HTTP as our client
|
||||
// - releaseStamp: the Meteor release name (for retrieval at runtime)
|
||||
// - isBare: at startup, just load the JavaScript and call main()
|
||||
// (rather than running a traditional script that initializes a HTTP
|
||||
// server and database connection in a hardcoded way)
|
||||
var ServerTarget = function (options) {
|
||||
var self = this;
|
||||
JsImageTarget.apply(this, arguments);
|
||||
|
||||
self.clientTarget = options.clientTarget;
|
||||
self.releaseStamp = options.releaseStamp;
|
||||
self.isBare = options.isBare;
|
||||
|
||||
if (! archinfo.matches(self.arch, "native"))
|
||||
throw new Error("ServerTarget targeting something that isn't a server?");
|
||||
@@ -1098,11 +1094,8 @@ _.extend(ServerTarget.prototype, {
|
||||
var imageControlFile = self.toJsImage().write(builder);
|
||||
|
||||
// Server bootstrap
|
||||
// XXX ideally, if isBare, should ensure that the app exports a
|
||||
// main() function and complain if not
|
||||
var bootScript = self.isBare ? 'boot-bare.js' : 'boot.js';
|
||||
builder.write('boot.js',
|
||||
{ file: path.join(__dirname, 'server', bootScript) });
|
||||
{ file: path.join(__dirname, 'server', 'boot.js') });
|
||||
|
||||
// Script that fetches the dev_bundle and runs the server bootstrap
|
||||
var archToPlatform = {
|
||||
@@ -1405,30 +1398,16 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
return client;
|
||||
};
|
||||
|
||||
var makeTraditionalServerTarget = function (app, clientTarget) {
|
||||
var server = new ServerTarget({
|
||||
var makeServerTarget = function (app, clientTarget) {
|
||||
var targetOptions = {
|
||||
library: library,
|
||||
arch: archinfo.host(),
|
||||
clientTarget: clientTarget,
|
||||
releaseStamp: options.releaseStamp
|
||||
});
|
||||
};
|
||||
if (clientTarget)
|
||||
targetOptions.clientTarget = clientTarget;
|
||||
|
||||
server.make({
|
||||
packages: [app],
|
||||
test: options.testPackages || [],
|
||||
minify: false
|
||||
});
|
||||
|
||||
return server;
|
||||
};
|
||||
|
||||
var makeBareServerTarget = function (app) {
|
||||
var server = new ServerTarget({
|
||||
library: library,
|
||||
arch: archinfo.host(),
|
||||
releaseStamp: options.releaseStamp,
|
||||
isBare: true
|
||||
});
|
||||
var server = new ServerTarget(targetOptions);
|
||||
|
||||
server.make({
|
||||
packages: [app],
|
||||
@@ -1452,7 +1431,7 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
targets.client = client;
|
||||
|
||||
// Server
|
||||
var server = makeTraditionalServerTarget(app, client);
|
||||
var server = makeServerTarget(app, client);
|
||||
targets.server = server;
|
||||
}
|
||||
|
||||
@@ -1506,7 +1485,7 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
|
||||
// Add to list
|
||||
programs.push({
|
||||
type: attrsJson.type || "bare",
|
||||
type: attrsJson.type || "server",
|
||||
name: item,
|
||||
path: itemPath,
|
||||
client: attrsJson.client,
|
||||
@@ -1532,8 +1511,8 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
library.override(p.name, p.path);
|
||||
var target;
|
||||
switch (p.type) {
|
||||
case "bare":
|
||||
target = makeBareServerTarget(p.name);
|
||||
case "server":
|
||||
target = makeServerTarget(p.name);
|
||||
break;
|
||||
case "traditional":
|
||||
var clientTarget;
|
||||
@@ -1556,7 +1535,7 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
// We don't check whether targets[p.client] is actually a
|
||||
// ClientTarget. If you want to be clever, go ahead.
|
||||
|
||||
target = makeTraditionalServerTarget(p.name, clientTarget);
|
||||
target = makeServerTarget(p.name, clientTarget);
|
||||
break;
|
||||
case "client":
|
||||
// We pass null for appDir because we are a
|
||||
@@ -1566,7 +1545,7 @@ exports.bundle = function (appDir, outputPath, options) {
|
||||
break;
|
||||
default:
|
||||
buildmessage.error(
|
||||
"type must be 'bare', 'traditional', or 'client'",
|
||||
"type must be 'server', 'traditional', or 'client'",
|
||||
{ file: p.attrsJsonRelPath });
|
||||
// recover by ignoring target
|
||||
return;
|
||||
|
||||
@@ -1479,7 +1479,7 @@ _.extend(Package.prototype, {
|
||||
var names = _.union(
|
||||
// standard client packages for the classic meteor stack.
|
||||
// XXX remove and make everyone explicitly declare all dependencies
|
||||
['meteor', 'deps', 'session', 'livedata', 'mongo-livedata',
|
||||
['meteor', 'webapp', 'deps', 'session', 'livedata', 'mongo-livedata',
|
||||
'spark', 'templating', 'startup', 'past', 'check'],
|
||||
project.get_packages(appDir));
|
||||
|
||||
|
||||
@@ -247,8 +247,8 @@ var startServer = function (options) {
|
||||
if (! options.program) {
|
||||
var nodeOptions = _.clone(options.nodeOptions);
|
||||
nodeOptions.push(path.join(options.bundlePath, 'main.js'));
|
||||
nodeOptions.push('--keepalive');
|
||||
nodeOptions.push('program.json');
|
||||
nodeOptions.push('--keepalive');
|
||||
|
||||
var child_process = require('child_process');
|
||||
var proc = child_process.spawn(process.execPath, nodeOptions,
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
// XXX this file is copied from boot.js. They should be unified one day.
|
||||
|
||||
var Fiber = require("fibers");
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var _ = require('underscore');
|
||||
|
||||
// read our control files
|
||||
var serverJson =
|
||||
JSON.parse(fs.readFileSync(path.join(__dirname, process.argv[2]), 'utf8'));
|
||||
var configJson =
|
||||
JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8'));
|
||||
|
||||
// Set up environment
|
||||
__meteor_bootstrap__ = { startup_hooks: [] };
|
||||
__meteor_runtime_config__ = { meteorRelease: configJson.release };
|
||||
|
||||
Fiber(function () {
|
||||
_.each(serverJson.load, function (fileInfo) {
|
||||
var code = fs.readFileSync(path.join(__dirname, fileInfo.path));
|
||||
|
||||
var Npm = {
|
||||
require: function (name) {
|
||||
if (! fileInfo.node_modules) {
|
||||
return require(name);
|
||||
}
|
||||
|
||||
var nodeModuleDir =
|
||||
path.join(__dirname, 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?");
|
||||
}
|
||||
}
|
||||
};
|
||||
// \n is necessary in case final line is a //-comment
|
||||
var wrapped = "(function(Npm){" + code + "\n})";
|
||||
|
||||
var func = require('vm').runInThisContext(wrapped, fileInfo.path, true);
|
||||
func.call(global, Npm); // 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 = [];
|
||||
if ('main' in global)
|
||||
mains.push(main);
|
||||
_.each(Package, function (p) {
|
||||
if ('main' in p)
|
||||
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);
|
||||
}
|
||||
process.exit(mains[0].call({}, process.argv.slice(3)));
|
||||
}).run();
|
||||
@@ -1,17 +1,6 @@
|
||||
////////// Requires //////////
|
||||
|
||||
var Fiber = require("fibers");
|
||||
|
||||
var fs = require("fs");
|
||||
var os = require("os");
|
||||
var path = require("path");
|
||||
var url = require("url");
|
||||
|
||||
var connect = require('connect');
|
||||
var gzippo = require('gzippo');
|
||||
var argv = require('optimist').boolean('keepalive').argv;
|
||||
var useragent = require('useragent');
|
||||
|
||||
var _ = require('underscore');
|
||||
|
||||
// This code is duplicated in tools/server/server.js.
|
||||
@@ -22,398 +11,84 @@ if (require('semver').lt(process.version, MIN_NODE_VERSION)) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Keepalives so that when the outer server dies unceremoniously and
|
||||
// doesn't kill us, we quit ourselves. A little gross, but better than
|
||||
// pidfiles.
|
||||
var initKeepalive = function () {
|
||||
var keepaliveCount = 0;
|
||||
// read our control files
|
||||
var serverJson =
|
||||
JSON.parse(fs.readFileSync(path.join(__dirname, process.argv[2]), 'utf8'));
|
||||
var configJson =
|
||||
JSON.parse(fs.readFileSync(path.join(__dirname, 'config.json'), 'utf8'));
|
||||
|
||||
process.stdin.on('data', function (data) {
|
||||
keepaliveCount = 0;
|
||||
});
|
||||
// Set up environment
|
||||
__meteor_bootstrap__ = {
|
||||
startup_hooks: [],
|
||||
serverDir: __dirname,
|
||||
configJson: configJson };
|
||||
__meteor_runtime_config__ = { meteorRelease: configJson.release };
|
||||
|
||||
process.stdin.resume();
|
||||
Fiber(function () {
|
||||
_.each(serverJson.load, function (fileInfo) {
|
||||
var code = fs.readFileSync(path.join(__dirname, fileInfo.path));
|
||||
|
||||
setInterval(function () {
|
||||
keepaliveCount ++;
|
||||
if (keepaliveCount >= 3) {
|
||||
console.log("Failed to receive keepalive! Exiting.");
|
||||
process.exit(1);
|
||||
}
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
|
||||
// #BrowserIdentification
|
||||
//
|
||||
// We have multiple places that want to identify the browser: the
|
||||
// unsupported browser page, the appcache package, and, eventually
|
||||
// delivering browser polyfills only as needed.
|
||||
//
|
||||
// To avoid detecting the browser in multiple places ad-hoc, we create a
|
||||
// Meteor "browser" object. It uses but does not expose the npm
|
||||
// useragent module (we could choose a different mechanism to identify
|
||||
// the browser in the future if we wanted to). The browser object
|
||||
// contains
|
||||
//
|
||||
// * `name`: the name of the browser in camel case
|
||||
// * `major`, `minor`, `patch`: integers describing the browser version
|
||||
//
|
||||
// Also here is an early version of a Meteor `request` object, intended
|
||||
// to be a high-level description of the request without exposing
|
||||
// details of connect's low-level `req`. Currently it contains:
|
||||
//
|
||||
// * `browser`: browser identification object described above
|
||||
// * `url`: parsed url, including parsed query params
|
||||
//
|
||||
// As a temporary hack there is a `categorizeRequest` function on
|
||||
// __meteor_bootstrap__ which converts a connect `req` to a Meteor
|
||||
// `request`. This can go away once smart packages such as appcache are
|
||||
// being passed a `request` object directly when they serve content.
|
||||
//
|
||||
// This allows `request` to be used uniformly: it is passed to the html
|
||||
// attributes hook, and the appcache package can use it when deciding
|
||||
// whether to generate a 404 for the manifest.
|
||||
//
|
||||
// Real routing / server side rendering will probably refactor this
|
||||
// heavily.
|
||||
|
||||
|
||||
// e.g. "Mobile Safari" => "mobileSafari"
|
||||
var camelCase = function (name) {
|
||||
var parts = name.split(' ');
|
||||
parts[0] = parts[0].toLowerCase();
|
||||
for (var i = 1; i < parts.length; ++i) {
|
||||
parts[i] = parts[i].charAt(0).toUpperCase() + parts[i].substr(1);
|
||||
}
|
||||
return parts.join('');
|
||||
};
|
||||
|
||||
var identifyBrowser = function (req) {
|
||||
var userAgent = useragent.lookup(req.headers['user-agent']);
|
||||
return {
|
||||
name: camelCase(userAgent.family),
|
||||
major: +userAgent.major,
|
||||
minor: +userAgent.minor,
|
||||
patch: +userAgent.patch
|
||||
};
|
||||
};
|
||||
|
||||
var categorizeRequest = function (req) {
|
||||
return {
|
||||
browser: identifyBrowser(req),
|
||||
url: url.parse(req.url, true)
|
||||
};
|
||||
};
|
||||
|
||||
var htmlAttributes = function (template, request) {
|
||||
var attributes = '';
|
||||
_.each(__meteor_bootstrap__.htmlAttributeHooks || [], function (hook) {
|
||||
var attribute = hook(request);
|
||||
if (attribute !== null && attribute !== undefined && attribute !== '')
|
||||
attributes += ' ' + attribute;
|
||||
});
|
||||
return template.replace('##HTML_ATTRIBUTES##', attributes);
|
||||
};
|
||||
|
||||
// Serve app HTML for this URL?
|
||||
var appUrl = function (url) {
|
||||
if (url === '/favicon.ico' || url === '/robots.txt')
|
||||
return false;
|
||||
|
||||
// NOTE: app.manifest is not a web standard like favicon.ico and
|
||||
// robots.txt. It is a file name we have chosen to use for HTML5
|
||||
// appcache URLs. It is included here to prevent using an appcache
|
||||
// then removing it from poisoning an app permanently. Eventually,
|
||||
// once we have server side routing, this won't be needed as
|
||||
// unknown URLs with return a 404 automatically.
|
||||
if (url === '/app.manifest')
|
||||
return false;
|
||||
|
||||
// Avoid serving app HTML for declared routes such as /sockjs/.
|
||||
if (__meteor_bootstrap__._routePolicy &&
|
||||
__meteor_bootstrap__._routePolicy.classify(url))
|
||||
return false;
|
||||
|
||||
// we currently return app HTML on all URLs by default
|
||||
return true;
|
||||
}
|
||||
|
||||
var run = function () {
|
||||
var serverDir = __dirname;
|
||||
|
||||
// read our control files
|
||||
var serverJson =
|
||||
JSON.parse(fs.readFileSync(path.join(serverDir, argv._[0]), 'utf8'));
|
||||
|
||||
var configJson =
|
||||
JSON.parse(fs.readFileSync(path.join(serverDir, 'config.json'), 'utf8'));
|
||||
|
||||
// read the control for the client we'll be serving up
|
||||
var clientJsonPath = path.join(serverDir, configJson.client);
|
||||
var clientDir = path.dirname(clientJsonPath);
|
||||
var clientJson = JSON.parse(fs.readFileSync(clientJsonPath, 'utf8'));
|
||||
|
||||
if (clientJson.format !== "browser-program-pre1")
|
||||
throw new Error("Unsupported format for client assets: " +
|
||||
JSON.stringify(clientJson.format));
|
||||
|
||||
var deployConfig =
|
||||
process.env.METEOR_DEPLOY_CONFIG
|
||||
? JSON.parse(process.env.METEOR_DEPLOY_CONFIG) : {};
|
||||
if (!deployConfig.packages)
|
||||
deployConfig.packages = {};
|
||||
if (!deployConfig.boot)
|
||||
deployConfig.boot = {};
|
||||
if (!deployConfig.boot.bind)
|
||||
deployConfig.boot.bind = {};
|
||||
|
||||
// check environment for legacy env variables.
|
||||
if (process.env.PORT && !_.has(deployConfig.boot.bind, 'localPort')) {
|
||||
deployConfig.boot.bind.localPort = parseInt(process.env.PORT);
|
||||
}
|
||||
// XXX make outer wrapper fail if MONGO_URL not set
|
||||
if (process.env.MONGO_URL) {
|
||||
if (!deployConfig.packages['mongo-livedata'])
|
||||
deployConfig.packages['mongo-livedata'] = {};
|
||||
deployConfig.packages['mongo-livedata'].url = process.env.MONGO_URL;
|
||||
}
|
||||
|
||||
// webserver
|
||||
var app = connect.createServer();
|
||||
|
||||
var staticCacheablePath = path.join(clientDir, clientJson.staticCacheable);
|
||||
if (staticCacheablePath)
|
||||
// cacheable files are files that should never change. Typically
|
||||
// named by their hash (eg meteor bundled js and css files).
|
||||
// cache them ~forever (1yr)
|
||||
//
|
||||
// 'root' option is to work around an issue in connect/gzippo.
|
||||
// See https://github.com/meteor/meteor/pull/852
|
||||
app.use(gzippo.staticGzip(staticCacheablePath,
|
||||
{clientMaxAge: 1000 * 60 * 60 * 24 * 365,
|
||||
root: '/'}));
|
||||
|
||||
// cache non-cacheable file anyway. This isn't really correct, as
|
||||
// users can change the files and changes won't propogate
|
||||
// immediately. However, if we don't cache them, browsers will
|
||||
// 'flicker' when rerendering images. Eventually we will probably want
|
||||
// to rewrite URLs of static assets to include a query parameter to
|
||||
// bust caches. That way we can both get good caching behavior and
|
||||
// allow users to change assets without delay.
|
||||
// https://github.com/meteor/meteor/issues/773
|
||||
var staticPath = path.join(clientDir, clientJson.static);
|
||||
if (staticPath)
|
||||
app.use(gzippo.staticGzip(staticPath,
|
||||
{clientMaxAge: 1000 * 60 * 60 * 24,
|
||||
root: '/'}));
|
||||
|
||||
// start up app
|
||||
__meteor_bootstrap__ = {
|
||||
app: app,
|
||||
// metadata about this bundle
|
||||
// XXX this could use some refactoring to better distinguish
|
||||
// server and client
|
||||
bundle: {
|
||||
manifest: clientJson.manifest,
|
||||
root: clientDir
|
||||
},
|
||||
// function that takes a connect `req` object and returns a summary
|
||||
// object with information about the request. See
|
||||
// #BrowserIdentifcation
|
||||
categorizeRequest: categorizeRequest,
|
||||
// list of functions to be called to determine any attributes to be
|
||||
// added to the '<html>' tag. Each function is passed a 'request'
|
||||
// object (see #BrowserIdentifcation) and should return a string,
|
||||
htmlAttributeHooks: [],
|
||||
// functions to be called after all packages are loaded and we are
|
||||
// ready to serve HTTP.
|
||||
startup_hooks: [],
|
||||
deployConfig: deployConfig
|
||||
};
|
||||
|
||||
__meteor_runtime_config__ = {};
|
||||
if (configJson.release) {
|
||||
__meteor_runtime_config__.meteorRelease = configJson.release;
|
||||
}
|
||||
|
||||
Fiber(function () {
|
||||
// (put in a fiber to let Meteor.db operations happen during loading)
|
||||
|
||||
// load app code
|
||||
_.each(serverJson.load, function (fileInfo) {
|
||||
var code = fs.readFileSync(path.join(serverDir, fileInfo.path));
|
||||
|
||||
var Npm = {
|
||||
// require an npm module used by your package, or one from the
|
||||
// dev bundle if you are in an app or your package isn't using
|
||||
// said npm module
|
||||
require: function (name) {
|
||||
if (! fileInfo.node_modules) {
|
||||
// current no support for npm outside packages. load from
|
||||
// dev bundle only
|
||||
return require(name);
|
||||
}
|
||||
|
||||
var nodeModuleDir =
|
||||
path.join(__dirname, 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 Npm = {
|
||||
require: function (name) {
|
||||
if (! fileInfo.node_modules) {
|
||||
return require(name);
|
||||
}
|
||||
};
|
||||
// \n is necessary in case final line is a //-comment
|
||||
var wrapped = "(function(Npm){" + code + "\n})";
|
||||
|
||||
// it's tempting to run the code in a new context so we can
|
||||
// precisely control the enviroment the user code sees. but,
|
||||
// this is harder than it looks. you get a situation where []
|
||||
// created in one runInContext invocation fails 'instanceof
|
||||
// Array' if tested in another (reusing the same context each
|
||||
// time fixes it for {} and Object, but not [] and Array.) and
|
||||
// we have no pressing need to do this, so punt.
|
||||
//
|
||||
// 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, fileInfo.path, true);
|
||||
// Setting `this` to `global` allows you to do a top-level
|
||||
// "this.foo = " to define global variables when using "use strict"
|
||||
// (http://es5.github.io/#x15.3.4.4); this is the only way to do
|
||||
// it in CoffeeScript.
|
||||
func.call(global, Npm);
|
||||
});
|
||||
var nodeModuleDir =
|
||||
path.join(__dirname, 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$/, '');
|
||||
|
||||
// Actually serve HTML. This happens after user code, so that
|
||||
// packages can insert connect middlewares and update
|
||||
// __meteor_runtime_config__
|
||||
var boilerplateHtmlPath = path.join(clientDir, clientJson.page);
|
||||
var boilerplateHtml =
|
||||
fs.readFileSync(boilerplateHtmlPath, 'utf8').replace(
|
||||
"// ##RUNTIME_CONFIG##",
|
||||
"__meteor_runtime_config__ = " +
|
||||
JSON.stringify(__meteor_runtime_config__) + ";");
|
||||
|
||||
app.use(function (req, res, next) {
|
||||
if (! appUrl(req.url))
|
||||
return next();
|
||||
|
||||
var request = categorizeRequest(req);
|
||||
|
||||
res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'});
|
||||
|
||||
var requestSpecificHtml = htmlAttributes(boilerplateHtml, request);
|
||||
res.write(requestSpecificHtml);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// Return 404 by default, if no other handlers serve this URL.
|
||||
app.use(function (req, res) {
|
||||
res.writeHead(404);
|
||||
res.end();
|
||||
});
|
||||
|
||||
// run the user startup hooks.
|
||||
_.each(__meteor_bootstrap__.startup_hooks, function (x) { x(); });
|
||||
|
||||
// only start listening after all the startup code has run.
|
||||
var bind = deployConfig.boot.bind;
|
||||
app.listen(bind.localPort || 0, function() {
|
||||
if (argv.keepalive)
|
||||
console.log("LISTENING"); // must match run.js
|
||||
var port = app.address().port;
|
||||
if (bind.viaProxy) {
|
||||
Fiber(function () {
|
||||
bindToProxy(port, bind.viaProxy);
|
||||
}).run();
|
||||
// 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?");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
// \n is necessary in case final line is a //-comment
|
||||
var wrapped = "(function(Npm){" + code + "\n})";
|
||||
|
||||
}).run();
|
||||
var func = require('vm').runInThisContext(wrapped, fileInfo.path, true);
|
||||
func.call(global, Npm); // Coffeescript
|
||||
});
|
||||
|
||||
if (argv.keepalive)
|
||||
initKeepalive();
|
||||
};
|
||||
// run the user startup hooks.
|
||||
_.each(__meteor_bootstrap__.startup_hooks, function (x) { x(); });
|
||||
|
||||
var bindToProxy = function (localPort, proxyConfig) {
|
||||
// XXX also support galaxy-based lookup
|
||||
if (!proxyConfig.proxyEndpoint)
|
||||
throw new Error("missing proxyEndpoint");
|
||||
if (!proxyConfig.bindHost)
|
||||
throw new Error("missing bindHost");
|
||||
// XXX move these into deployConfig?
|
||||
if (!process.env.GALAXY_JOB)
|
||||
throw new Error("missing $GALAXY_JOB");
|
||||
if (!process.env.LAST_START)
|
||||
throw new Error("missing $LAST_START");
|
||||
|
||||
// XXX rename pid argument to bindTo.
|
||||
var pid = {
|
||||
job: process.env.GALAXY_JOB,
|
||||
lastStarted: process.env.LAST_START
|
||||
};
|
||||
var myHost = os.hostname();
|
||||
|
||||
var ddpBindTo = proxyConfig.unprivilegedPorts ? {
|
||||
ddpUrl: 'ddp://' + proxyConfig.bindHost + ':4433/',
|
||||
insecurePort: 8080
|
||||
} : {
|
||||
ddpUrl: 'ddp://' + proxyConfig.bindHost + '/'
|
||||
};
|
||||
|
||||
var proxy = Package.meteor.Meteor.connect(proxyConfig.proxyEndpoint);
|
||||
var route = process.env.ROUTE;
|
||||
var host = route.split(":")[0];
|
||||
var port = route.split(":")[1];
|
||||
proxy.call('bindDdp', {
|
||||
pid: pid,
|
||||
bindTo: ddpBindTo,
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port,
|
||||
pathPrefix: '/websocket'
|
||||
// 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);
|
||||
}
|
||||
});
|
||||
proxy.call('bindHttp', {
|
||||
pid: pid,
|
||||
bindTo: {
|
||||
host: proxyConfig.bindHost,
|
||||
port: proxyConfig.unprivilegedPorts ? 8080 : 80
|
||||
},
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
proxy.call('bindHttp', {
|
||||
pid: pid,
|
||||
bindTo: {
|
||||
host: proxyConfig.bindHost,
|
||||
port: proxyConfig.unprivilegedPorts ? 4433: 443,
|
||||
ssl: true
|
||||
},
|
||||
proxyTo: {
|
||||
host: host,
|
||||
port: port
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
run();
|
||||
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();
|
||||
|
||||
Reference in New Issue
Block a user