Merge branch 'devel' into shark

This commit is contained in:
David Greenspan
2013-08-13 16:21:28 -07:00
35 changed files with 453 additions and 116 deletions

View File

@@ -1,6 +1,15 @@
## vNEXT
## v0.6.5
* linker! namespacing, exports, unipackages, weak and unordered dependencies,
etc. sourcemaps (including for coffee). standard-app-packages. don't
implicitly use all app packages. lots of stuff moved from server.js (now
boot.js) to webapp package. plugins!
* Log
* Fix Mongo selectors of the form: {$regex: /foo/}.
* Calling `findOne()` on the server no longer loads the full query result
@@ -10,7 +19,16 @@
* Upgraded dependencies:
* Node from 0.8.18 to 0.8.24
* MongoDB from 2.4.3 to 2.4.4
* MongoDB from 2.4.3 to 2.4.4, now with SSL support
* CleanCSS from 0.8.3 to 1.0.11
* Underscore from 1.4.4 to 1.5.1
* Fibers from 1.0.0 to 1.0.1
* When removing the last NPM dependency, clean up the `.npm` dir
* `$ROOT_URL` may now have a path part
* `new Meteor.Collection("name", {connection: null})` works
* Make server-side Mongo inserts, updates, and removes run asynchronously when a
callback is passed.
@@ -26,6 +44,29 @@
- `Meteor.connect` - `DDP.connect`
- `Meteor.http` - `HTTP`
* The `observe` callback `movedTo` now has the fourth argument `before`.
* The `client/compatibility` thing added in 0.6.3 could be used from package.js
by passing the `raw` option to `add_files`; this is renamed to `bare`
* Fix EPIPEs during dev mode hot code reload
* Fix bug where we would never quiesce if we tried to revive subs that errored
out (5e7138d)
* Implement "meteor bundle --debug" #748
bugs to describe:
#1151 (Meteor.disconnect etc)
#1106
#1143
#1191
#1226
#1181
/sockjs/info cache buster (for Chrome bug)
Patches contributed by GitHub users btipling, mizzao, timhaines and zol.
## v0.6.4.1

View File

@@ -1 +1 @@
0.6.5-rc2
galaxy-6

View File

@@ -2348,6 +2348,10 @@ Matches any value.
Matches a primitive of the given type.
{{/dtdd}}
{{#dtdd "<code>Match.Integer</code>"}}
Matches a signed 32-bit integer. Doesn't match `Infinity`, `-Infinity`, or `NaN`.
{{/dtdd}}
{{#dtdd "<code>[<em>pattern</em>]</code>"}}
A one-element array matches an array of elements, each of which match
*pattern*. For example, `[Number]` matches a (possibly empty) array of numbers;
@@ -2371,8 +2375,19 @@ Matches any plain Object with any keys; equivalent to
`Match.ObjectIncluding({})`.
{{/dtdd}}
{{#dtdd "<code>Match.Optional(<em>pattern</em>)</code>"}}
Matches either `undefined` or something that matches *pattern*.
{{#dtdd "<code>Match.Optional(<em>pattern</em>)</code>"}} Matches either
`undefined` or something that matches pattern. If used in an object this matches
only if the key is not set as opposed to the value being set to `undefined`.
// In an object
var pat = { name: Match.Optional(String) };
check({ name: "something" }, pat) // OK
check({}, pat) // OK
check({ name: undefined }, pat) // Throws an exception
// Outside an object
check(undefined, Match.Optional(String)); // OK
{{/dtdd}}
{{#dtdd "<code>Match.OneOf(<em>pattern1</em>, <em>pattern2</em>, ...)</code>"}}

View File

@@ -1,5 +1,4 @@
// XXX docs
// XXX on linker branch, export Match and check
// Things we explicitly do NOT support:
// - heterogenous arrays
@@ -11,7 +10,13 @@ check = function (value, pattern) {
var argChecker = currentArgumentChecker.get();
if (argChecker)
argChecker.checking(value);
checkSubtree(value, pattern);
try {
checkSubtree(value, pattern);
} catch (err) {
if ((err instanceof Match.Error) && err.path)
err.message += " in field " + err.path;
throw err;
}
};
Match = {
@@ -28,11 +33,17 @@ Match = {
ObjectIncluding: function (pattern) {
return new ObjectIncluding(pattern);
},
// Matches only signed 32-bit integers
Integer: ['__integer__'],
// XXX should we record the path down the tree in the error message?
// XXX matchers should know how to describe themselves for errors
Error: Meteor.makeErrorType("Match.Error", function (msg) {
this.message = "Match error: " + msg;
// The path of the value that failed to match. Initially empty, this gets
// populated by catching and rethrowing the exception as it goes back up the
// stack.
// E.g.: "vals[3].entity.created"
this.path = "";
// If this gets sent over DDP, don't give full internal details but at least
// provide something better than 500 Internal server error.
this.sanitizedError = new Meteor.Error(400, "Match failed");
@@ -119,6 +130,20 @@ var checkSubtree = function (value, pattern) {
throw new Match.Error("Expected null, got " + EJSON.stringify(value));
}
// Match.Integer is special type encoded with array
if (pattern === Match.Integer) {
// There is no consistent and reliable way to check if variable is a 64-bit
// integer. One of the popular solutions is to get reminder of division by 1
// but this method fails on really large floats with big precision.
// E.g.: 1.348192308491824e+23 % 1 === 0 in V8
// Bitwise operators work consistantly but always cast variable to 32-bit
// signed integer according to JavaScript specs.
if (typeof value === "number" && (value | 0) === value)
return
throw new Match.Error("Expected Integer, got "
+ (value instanceof Object ? EJSON.stringify(value) : value));
}
// "Object" is shorthand for Match.ObjectIncluding({});
if (pattern === Object)
pattern = Match.ObjectIncluding({});
@@ -132,8 +157,15 @@ var checkSubtree = function (value, pattern) {
throw new Match.Error("Expected array, got " + EJSON.stringify(value));
}
_.each(value, function (valueElement) {
checkSubtree(valueElement, pattern[0]);
_.each(value, function (valueElement, index) {
try {
checkSubtree(valueElement, pattern[0]);
} catch (err) {
if (err instanceof Match.Error) {
err.path = _prependPath(index, err.path);
}
throw err;
}
});
return;
}
@@ -206,14 +238,20 @@ var checkSubtree = function (value, pattern) {
});
_.each(value, function (subValue, key) {
if (_.has(requiredPatterns, key)) {
checkSubtree(subValue, requiredPatterns[key]);
delete requiredPatterns[key];
} else if (_.has(optionalPatterns, key)) {
checkSubtree(subValue, optionalPatterns[key]);
} else {
if (!unknownKeysAllowed)
throw new Match.Error("Unknown key '" + key + "'");
try {
if (_.has(requiredPatterns, key)) {
checkSubtree(subValue, requiredPatterns[key]);
delete requiredPatterns[key];
} else if (_.has(optionalPatterns, key)) {
checkSubtree(subValue, optionalPatterns[key]);
} else {
if (!unknownKeysAllowed)
throw new Match.Error("Unknown key");
}
} catch (err) {
if (err instanceof Match.Error)
err.path = _prependPath(key, err.path);
throw err;
}
});
@@ -266,3 +304,25 @@ _.extend(ArgumentChecker.prototype, {
self.description);
}
});
var _jsKeywords = ["do", "if", "in", "for", "let", "new", "try", "var", "case",
"else", "enum", "eval", "false", "null", "this", "true", "void", "with",
"break", "catch", "class", "const", "super", "throw", "while", "yield",
"delete", "export", "import", "public", "return", "static", "switch",
"typeof", "default", "extends", "finally", "package", "private", "continue",
"debugger", "function", "arguments", "interface", "protected", "implements",
"instanceof"];
// Assumes the base of path is already escaped properly
// returns key + base
var _prependPath = function (key, base) {
if ((typeof key) === "number" || key.match(/^[0-9]+$/))
key = "[" + key + "]";
else if (!key.match(/^[a-z_$][0-9a-z_$]*$/i) || _.contains(_jsKeywords, key))
key = JSON.stringify([key]);
if (base && base[0] !== "[")
return key + '.' + base;
return key + base;
};

View File

@@ -122,6 +122,24 @@ Tinytest.add("check - check", function (test) {
x: Number,
k: Match.OneOf(null, Boolean)})]});
// Match.Integer
matches(-1, Match.Integer);
matches(0, Match.Integer);
matches(1, Match.Integer);
matches(-2147483648, Match.Integer); // INT_MIN
matches(2147483647, Match.Integer); // INT_MAX
fails(123.33, Match.Integer);
fails(.33, Match.Integer);
fails(1.348192308491824e+23, Match.Integer);
fails(NaN, Match.Integer);
fails(Infinity, Match.Integer);
fails(-Infinity, Match.Integer);
fails({}, Match.Integer);
fails([], Match.Integer);
fails(function () {}, Match.Integer);
fails(new Date, Match.Integer);
// Test that "arguments" is treated like an array.
var argumentsMatches = function () {
matches(arguments, [Number]);
@@ -199,3 +217,43 @@ Tinytest.add("check - argument checker", function (test) {
check(x, Boolean);
}, true, true);
});
Tinytest.add("check - Match error path", function (test) {
var match = function (value, pattern, expectedPath) {
try {
check(value, pattern);
} catch (err) {
// XXX just for FF 3.6, its JSON stringification prefers "\u000a" to "\n"
err.path = err.path.replace(/\\u000a/, "\\n");
if (err.path != expectedPath)
test.fail({
type: "match-error-path",
message: "The path of Match.Error doesn't match.",
pattern: JSON.stringify(pattern),
value: JSON.stringify(value),
path: err.path,
expectedPath: expectedPath
});
}
};
match({ foo: [ { bar: 3 }, {bar: "something"} ] }, { foo: [ { bar: Number } ] }, "foo[1].bar");
// Complicated case with arrays, $, whitespace and quotes!
match([{ $FoO: { "bar baz\n\"'": 3 } }], [{ $FoO: { "bar baz\n\"'": String } }], "[0].$FoO[\"bar baz\\n\\\"'\"]");
// Numbers only, can be accessed w/o quotes
match({ "1231": 123 }, { "1231": String }, "[1231]");
match({ "1234abcd": 123 }, { "1234abcd": String }, "[\"1234abcd\"]");
match({ $set: { people: "nice" } }, { $set: { people: [String] } }, "$set.people");
match({ _underscore: "should work" }, { _underscore: Number }, "_underscore");
// Nested array looks nice
match([[["something", "here"], []], [["string", 123]]], [[[String]]], "[1][0][1]");
// Object nested in arrays should look nice, too!
match([[[{ foo: "something" }, { foo: "here"}],
[{ foo: "asdf" }]],
[[{ foo: 123 }]]],
[[[{ foo: String }]]], "[1][0][0].foo");
// JS keyword
match({ "return": 0 }, { "return": String }, "[\"return\"]");
});

View File

@@ -49,7 +49,7 @@ Ctl.Commands.push({
if (appConfig.admin) {
bindPathPrefix = "/" + Ctl.myAppName();
proxyConfig = {
securePort: null,
securePort: 44333,
insecurePort: 9414,
bindHost: "localhost",
bindPathPrefix: bindPathPrefix
@@ -73,7 +73,8 @@ Ctl.Commands.push({
"email": {
url: appConfig.MAIL_URL
}
}
},
proxyServiceName: appConfig.proxyServiceName || "proxy"
};
// Merge in any values that might have been added to the app's config in
@@ -94,7 +95,8 @@ Ctl.Commands.push({
bindEnv: "PORT",
routeEnv: "ROUTE"
}
}
},
tags: ["runner"]
}]);
console.log("Started a server.");
} else {

View File

@@ -59,7 +59,8 @@ var getTokenResponse = function (query) {
}
}).content;
} catch (err) {
throw new Error("Failed to complete OAuth handshake with Facebook. " + err.message);
throw _.extend(new Error("Failed to complete OAuth handshake with Facebook. " + err.message),
{response: err.response});
}
// If 'responseContent' parses as JSON, it is an error.
@@ -89,7 +90,8 @@ var getIdentity = function (accessToken) {
return HTTP.get("https://graph.facebook.com/me", {
params: {access_token: accessToken}}).data;
} catch (err) {
throw new Error("Failed to fetch identity from Facebook. " + err.message);
throw _.extend(new Error("Failed to fetch identity from Facebook. " + err.message),
{response: err.response});
}
};

View File

@@ -43,7 +43,8 @@ var getAccessToken = function (query) {
}
});
} catch (err) {
throw new Error("Failed to complete OAuth handshake with Github. " + err.message);
throw _.extend(new Error("Failed to complete OAuth handshake with Github. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
throw new Error("Failed to complete OAuth handshake with GitHub. " + response.data.error);
@@ -60,7 +61,8 @@ var getIdentity = function (accessToken) {
params: {access_token: accessToken}
}).data;
} catch (err) {
throw new Error("Failed to fetch identity from GitHub. " + err.message);
throw _.extend(new Error("Failed to fetch identity from Github. " + err.message),
{response: err.response});
}
};

View File

@@ -51,7 +51,8 @@ var getTokens = function (query) {
grant_type: 'authorization_code'
}});
} catch (err) {
throw new Error("Failed to complete OAuth handshake with Google. " + err.message);
throw _.extend(new Error("Failed to complete OAuth handshake with Google. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
@@ -71,7 +72,8 @@ var getIdentity = function (accessToken) {
"https://www.googleapis.com/oauth2/v1/userinfo",
{params: {access_token: accessToken}}).data;
} catch (err) {
throw new Error("Failed to fetch identity from Google. " + err.message);
throw _.extend(new Error("Failed to fetch identity from Google. " + err.message),
{response: err.response});
}
};

View File

@@ -0,0 +1,3 @@
// Do we already have a global JSON object? Export it as our JSON object.
if (window.JSON)
JSON = window.JSON;

View File

@@ -3,10 +3,12 @@ Package.describe({
internal: true
});
// We need to figure out how to serve this file only to browsers that
// don't have JSON.stringify (eg, IE7 and earlier -- or is that IE8?)
// We need to figure out how to serve this file only to browsers that don't have
// JSON.stringify (eg, IE7 and earlier, and IE8 outside of "standards mode")
Package.on_use(function (api) {
// Node always has JSON; we only need this in some browsers.
api.export('JSON', 'client');
api.add_files('json_native.js', 'client');
api.add_files('json2.js', 'client');
});

View File

@@ -1,8 +1,18 @@
var fs = Npm.require('fs');
var path = Npm.require('path');
var less = Npm.require('less');
var Future = Npm.require('fibers/future');
Plugin.registerSourceHandler("less", function (compileStep) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}
var source = compileStep.read().toString('utf8');
var options = {
// Use fs.readFileSync to process @imports. This is the bundler, so
@@ -13,24 +23,28 @@ Plugin.registerSourceHandler("less", function (compileStep) {
paths: [path.dirname(compileStep._fullInputPath)] // for @import
};
var f = new Future;
var css;
try {
less.render(source, options, function (err, css) {
if (err) {
// XXX better error handling, once the Plugin interface support it
throw new Error(err.message);
}
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});
less.render(source, options, f.resolver());
css = f.wait();
} catch (e) {
// less.render() is supposed to report any errors via its
// callback. But sometimes, it throws them instead. This is
// probably a bug in less. Be prepared for either behavior.
throw new Error(source_path + ": Less compiler error: " + e.message);
compileStep.error({
message: "Less compiler error: " + e.message,
sourcePath: e.filename || compileStep.inputPath,
line: e.line - 1, // dunno why, but it matches
column: e.column + 1
});
return;
}
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});;
// Register lessimport files with the dependency watcher, without actually

View File

@@ -1287,18 +1287,17 @@ Tinytest.add("livedata connection - onReconnect prepends messages correctly with
var getSelfConnectionUrl = function () {
if (Meteor.isClient) {
return "/";
return Meteor._relativeToSiteRootUrl("/");
} else {
return Meteor.absoluteUrl();
}
};
if (Meteor.isServer) {
var reversed = {};
Meteor.methods({
reverse: function (arg) {
reversed[arg] = true;
return arg.split("").reverse().join("");
// Return something notably different from reverse.meteor.com.
return arg.split("").reverse().join("") + " LOCAL";
}
});
}
@@ -1325,22 +1324,10 @@ testAsyncMulti("livedata connection - reconnect to a different server", [
if (self.doTest) {
self.conn.reconnect({url: getSelfConnectionUrl()});
self.conn.call("reverse", "bar", expect(function (err, res) {
test.equal(res, "rab");
}));
}
},
function (test, expect) {
var self = this;
var id = Random.id();
if (self.doTest) {
self.conn.call("reverse", id, expect(function (err, res) {
if (Meteor.isServer) {
test.isTrue(reversed[id]);
}
test.equal(res, "rab LOCAL");
}));
}
}
]);
Tinytest.addAsync("livedata connection - version negotiation requires renegotiating",

View File

@@ -600,6 +600,8 @@ if (Meteor.isClient) {
]);
}
var selfUrl = Meteor.isServer
? Meteor.absoluteUrl() : Meteor._relativeToSiteRootUrl('/');
if (Meteor.isServer) {
Meteor.methods({
@@ -613,7 +615,7 @@ if (Meteor.isServer) {
testAsyncMulti("livedata - connect works from both client and server", [
function (test, expect) {
var self = this;
self.conn = DDP.connect(Meteor.absoluteUrl());
self.conn = DDP.connect(selfUrl);
pollUntil(expect, function () {
return self.conn.status().connected;
}, 10000);
@@ -637,7 +639,7 @@ if (Meteor.isServer) {
testAsyncMulti("livedata - method call on server blocks in a fiber way", [
function (test, expect) {
var self = this;
self.conn = DDP.connect(Meteor.absoluteUrl());
self.conn = DDP.connect(selfUrl);
pollUntil(expect, function () {
return self.conn.status().connected;
}, 10000);

View File

@@ -23,9 +23,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
// <METEOR> Commented out JSO implementation (use json package instead).
// JSON2 by Douglas Crockford (minified).
var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
// var JSON;JSON||(JSON={}),function(){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function f(a){return a<10?"0"+a:a}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")})}()
// </METEOR>
// [*] Including lib/index.js
// Public object

View File

@@ -101,7 +101,7 @@ _.extend(LivedataTest.ClientStream.prototype, {
self._clearConnectionAndHeartbeatTimers();
if (self.socket) {
self.socket.onmessage = self.socket.onclose
= self.socket.onerror = function () {};
= self.socket.onerror = self.socket.onheartbeat = function () {};
self.socket.close();
self.socket = null;
}

View File

@@ -247,7 +247,7 @@ Log.format = function (obj, options) {
'-',
timeStamp,
utcOffsetStr,
timeInexact ? '?' : ' ',
timeInexact ? '? ' : ' ',
appInfo,
sourceInfo,
stderrIndicator].join('');

View File

@@ -51,7 +51,7 @@ Tinytest.add("logging - log", function (test) {
[0, "0", "falsy - 0"],
[null, "null", "falsy - null"],
[undefined, "undefined", "falsy - undefined"],
["2013-06-13T01:15:16.000Z", new Date("2013-06-13T01:15:16.000Z"), "date"],
[new Date("2013-06-13T01:15:16.000Z"), new Date("2013-06-13T01:15:16.000Z"), "date"],
[/[^regexp]{0,1}/g, "/[^regexp]{0,1}/g", "regexp"],
[true, "true", "boolean - true"],
[false, "false", "boolean - false"],
@@ -72,7 +72,13 @@ Tinytest.add("logging - log", function (test) {
var recieved = intercepted[index];
var obj = EJSON.parse(recieved);
if (_.isDate(expected))
// IE8 and old Safari don't support this date format. Skip it.
if (expected && expected.toString &&
(expected.toString() === "NaN" ||
expected.toString() === "Invalid Date"))
return;
if (_.isDate(testcase[0]))
obj.message = new Date(obj.message);
test.equal(obj.message, expected, 'Logging ' + testName);
});
@@ -137,7 +143,7 @@ Tinytest.add("logging - format", function (test) {
test.equal(
Log.format({message: "message", time: time, timeInexact: true, level: level}),
level.charAt(0).toUpperCase() + "20120908-07:06:05.004" + utcOffsetStr + "?message");
level.charAt(0).toUpperCase() + "20120908-07:06:05.004" + utcOffsetStr + "? message");
test.equal(
Log.format({foo1: "bar1", foo2: "bar2", time: time, level: level}),

View File

@@ -31,7 +31,8 @@ var getAccessToken = function (query) {
state: query.state
}});
} catch (err) {
throw new Error("Failed to complete OAuth handshake with Meetup. " + err.message);
throw _.extend(new Error("Failed to complete OAuth handshake with Meetup. " + err.message),
{response: err.response});
}
if (response.data.error) { // if the http response was a json object with an error attribute
@@ -48,7 +49,8 @@ var getIdentity = function (accessToken) {
{params: {member_id: 'self', access_token: accessToken}});
return response.data.results && response.data.results[0];
} catch (err) {
throw new Error("Failed to fetch identity from Meetup: " + err.message);
throw _.extend(new Error("Failed to fetch identity from Meetup. " + err.message),
{response: err.response});
}
};

View File

@@ -3,11 +3,12 @@
source file. */
Plugin.registerSourceHandler("css", function (compileStep) {
// XXX use archinfo rather than rolling our own
if (! compileStep.arch.match(/^browser(\.|$)/)) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}

View File

@@ -1,10 +1,10 @@
{
"dependencies": {
"mongodb": {
"version": "1.3.12",
"from": "https://github.com/mongodb/node-mongodb-native/tarball/02a5723aa51e9fdbad743a1117655e535880b3db",
"dependencies": {
"bson": {
"version": "0.2.1"
"version": "0.2.2"
},
"kerberos": {
"version": "0.0.3"

View File

@@ -12,7 +12,10 @@ Package.describe({
internal: true
});
Npm.depends({mongodb: "1.3.12"});
// Use soon-to-be-released mongo driver version 1.3.16. It includes a
// fix for a connection storm that impacted production hosting.
// Change this to 1.3.16 once NPM is updated.
Npm.depends({mongodb: "https://github.com/mongodb/node-mongodb-native/tarball/02a5723aa51e9fdbad743a1117655e535880b3db"});
Package.on_use(function (api) {
api.use(['random', 'ejson', 'json', 'underscore', 'minimongo', 'logging',

View File

@@ -123,7 +123,8 @@ OAuth1Binding.prototype._call = function(method, url, headers, params, callback)
}
}, callback);
} catch (err) {
throw new Error("Failed to send OAuth1 request to " + url + ". " + err.message);
throw _.extend(new Error("Failed to send OAuth1 request to " + url + ". " + err.message),
{response: err.response});
}
};

View File

@@ -12,7 +12,7 @@ Package._transitional_registerBuildPlugin({
});
Package.on_test(function (api) {
api.use(['tinytest', 'stylus', 'test-helpers'])
api.use(['tinytest', 'stylus', 'test-helpers']);
api.use('spark');
api.add_files(['stylus_tests.styl', 'stylus_tests.js'], 'client');
});

View File

@@ -1,21 +1,34 @@
var fs = Npm.require('fs');
var stylus = Npm.require('stylus');
var nib = Npm.require('nib');
var Future = Npm.require('fibers/future');
Plugin.registerSourceHandler("styl", function (compileStep) {
// XXX annoying that this is replicated in .css, .less, and .styl
if (! compileStep.archMatches('browser')) {
// XXX in the future, might be better to emit some kind of a
// warning if a stylesheet is included on the server, rather than
// silently ignoring it. but that would mean you can't stick .css
// at the top level of your app, which is kind of silly.
return;
}
var f = new Future;
stylus(compileStep.read().toString('utf8'))
.use(nib())
.set('filename', compileStep.inputPath)
.render(function(err, output) {
if (err) {
// XXX better error handling, once the Plugin interface support it
throw new Error('Stylus compiler error: ' + err.message);
}
.render(f.resolver());
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: output
});
try {
var css = f.wait();
} catch (e) {
compileStep.error({
message: "Stylus compiler error: " + e.message
});
return;
}
);
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css
});
});

View File

@@ -21,7 +21,6 @@ var log = function (/*arguments*/) {
};
var finished = 0;
var passed = 0;
var failed = 0;
var expected = 0;
@@ -96,6 +95,14 @@ Meteor.startup(function () {
if (resultSet[name].status !== "FAIL")
resultSet[name].status = "EXPECTED";
break;
case "exception":
log(name, ":", "!!!!!!!!! FAIL !!!!!!!!!!!");
if (event.details && event.details.stack)
log(event.details.stack);
else
log("Test failed with exception");
failed++;
break;
case "finish":
switch (resultSet[name].status) {
case "OK":
@@ -120,7 +127,6 @@ Meteor.startup(function () {
default:
log(name, ": unknown state for the test to be in");
}
finished++;
break;
default:
resultSet[name].status = "FAIL";

View File

@@ -4,7 +4,7 @@ Package.describe({
});
Package.on_use(function (api) {
api.use(['underscore'], 'client');
api.use(['underscore', 'domutils'], 'client');
api.export('UniversalEventListener', 'client');
api.add_files(['listener.js',
'events-w3c.js',

View File

@@ -452,7 +452,8 @@ var runWebAppServer = function () {
// bind via the proxy, but we'll have to find it ourselves via
// ultraworld.
var galaxy = findGalaxy();
galaxy.subscribe('servicesByName', 'proxy');
var proxyServiceName = deployConfig.proxyServiceName || "proxy";
galaxy.subscribe('servicesByName', proxyServiceName);
var Proxies = new Meteor.Collection('services', {
manager: galaxy
});
@@ -462,7 +463,7 @@ var runWebAppServer = function () {
WebAppInternals.bindToProxy(_.extend({
proxyEndpoint: proxyService.providers.proxy
}, bind.viaProxy));
}
}
};
Proxies.find().observe({
added: doBinding,
@@ -520,6 +521,29 @@ WebAppInternals.bindToProxy = function (proxyConfig) {
var route = process.env.ROUTE;
var host = route.split(":")[0];
var port = +route.split(":")[1];
var completedBindings = {
ddp: false,
http: false,
https: proxyConfig.securePort !== null ? false : undefined
};
var bindingDoneCallback = function (binding) {
return function (err, resp) {
if (err)
throw err;
completedBindings[binding] = true;
var completedAll = _.every(_.keys(completedBindings), function (binding) {
return (completedBindings[binding] ||
completedBindings[binding] === undefined);
});
if (completedAll)
Log("Bound to proxy.");
return completedAll;
};
};
proxy.call('bindDdp', {
pid: pid,
bindTo: ddpBindTo,
@@ -528,7 +552,7 @@ WebAppInternals.bindToProxy = function (proxyConfig) {
port: port,
pathPrefix: bindPathPrefix + '/websocket'
}
});
}, bindingDoneCallback("ddp"));
proxy.call('bindHttp', {
pid: pid,
bindTo: {
@@ -541,7 +565,7 @@ WebAppInternals.bindToProxy = function (proxyConfig) {
port: port,
pathPrefix: bindPathPrefix
}
});
}, bindingDoneCallback("http"));
if (proxyConfig.securePort !== null) {
proxy.call('bindHttp', {
pid: pid,
@@ -556,9 +580,8 @@ WebAppInternals.bindToProxy = function (proxyConfig) {
port: port,
pathPrefix: bindPathPrefix
}
});
}, bindingDoneCallback("https"));
}
Log("Bound to proxy");
};
runWebAppServer();

View File

@@ -46,7 +46,8 @@ var getTokenResponse = function (query) {
grant_type: 'authorization_code'
}});
} catch (err) {
throw new Error("Failed to complete OAuth handshake with Weibo. " + err.message);
throw _.extend(new Error("Failed to complete OAuth handshake with Weibo. " + err.message),
{response: err.response});
}
// result.headers["content-type"] is 'text/plain;charset=UTF-8', so
@@ -66,7 +67,8 @@ var getIdentity = function (accessToken, userId) {
"https://api.weibo.com/2/users/show.json",
{params: {access_token: accessToken, uid: userId}}).data;
} catch (err) {
throw new Error("Failed to fetch identity from Weibo. " + err.message);
throw _.extend(new Error("Failed to fetch identity from Weibo. " + err.message),
{response: err.response});
}
};

View File

@@ -130,15 +130,18 @@ exports.deleteApp = function (app, context) {
// so we can be careful to not rely on any of the app dir context when
// in --star mode.
exports.deploy = function (options) {
var galaxy = getGalaxy(options.context);
var Package = getPackage(options.context);
var tmpdir = files.mkdtemp('deploy');
var buildDir = path.join(tmpdir, 'build');
var topLevelDirName = path.basename(options.appDir);
var bundlePath = path.join(buildDir, topLevelDirName);
var bundler = require('./bundler.js');
var starball;
// Don't try to connect to galaxy before the bundle is done. Because bundling
// doesn't yield, this will cause the connection to timeout. Eventually we'd
// like to have bundle yield, so that we can connect (and make sure auth
// works) before bundling.
if (!options.starball) {
process.stdout.write('Deploying ' + options.app + '. Bundling...\n');
var bundleResult = bundler.bundle(options.appDir, bundlePath,
@@ -166,6 +169,10 @@ exports.deploy = function (options) {
}
process.stdout.write('Uploading...\n');
var galaxy = getGalaxy(options.context);
var Package = getPackage(options.context);
var created = true;
var appConfig = {
METEOR_SETTINGS: options.settings

View File

@@ -110,9 +110,25 @@ _.extend(Library.prototype, {
for (var i = 0; i < self.localPackageDirs.length; ++i) {
var packageDir = path.join(self.localPackageDirs[i], name);
// XXX or unipackage.json? see also watchLocalPackageDirs
if (fs.existsSync(path.join(packageDir, 'package.js')))
// A directory is a package if it either contains 'package.js' (a package
// source tree) or 'unipackage.json' (a compiled unipackage). (Actually,
// for now, unipackages contain a dummy package.js too.)
//
// XXX support for putting unipackages in a local package dir is
// incomplete! They will be properly loaded, but other packages that
// depend on them have no way of knowing when they change! unipackages
// that are the .build of a source tree work fine (they have a
// buildinfo.json and can be rebuilt), and warehouse unipackages work fine
// too (users are not supposed to edit them (they are read-only on disk),
// and their pathname specifies a version). But if you, eg, have a
// unipackage of coffeescript in a local package directory, build another
// package dependending on it, and substitute another version of the
// unipackage in the same location, nothing will ever rebuild your
// package!
if (fs.existsSync(path.join(packageDir, 'package.js')) ||
fs.existsSync(path.join(packageDir, 'unipackage.json'))) {
return packageDir;
}
}
// Try the Meteor distribution, if we have one.
@@ -120,6 +136,8 @@ _.extend(Library.prototype, {
if (version) {
packageDir = path.join(warehouse.getWarehouseDir(),
'packages', name, version);
// The warehouse is theoretically constructed carefully enough that the
// directory really should not exist unless it is complete.
if (! fs.existsSync(packageDir))
throw new Error("Package missing from warehouse: " + name +
" version " + version);
@@ -157,6 +175,20 @@ _.extend(Library.prototype, {
return self.loadedPackages[name].pkg;
}
// Check for invalid package names. Currently package names can only
// contain ASCII alphanumerics and dash, and must contain at least
// one non-digit-or-dash.
//
// We don't support '.' because it is used as the separator between
// a package name and a slice. This might want to change.
//
// XXX revisit this later. What about unicode package names?
if (/[^A-Za-z0-9\-]/.test(name) || !/[A-Za-z]/.test(name) ) {
if (throwOnError === false)
return null;
throw new Error("Invalid package name: " + name);
}
var packageDir = self.findPackageDirectory(name);
if (! packageDir) {
@@ -224,8 +256,15 @@ _.extend(Library.prototype, {
if (! buildmessage.jobHasMessages() && // ensure no errors!
pkg.canBeSavedAsUnipackage()) {
// Save it, for a fast load next time
files.add_to_gitignore(packageDir, '.build*');
pkg.saveAsUnipackage(buildDir, { buildOfPath: packageDir });
try {
files.add_to_gitignore(packageDir, '.build*');
pkg.saveAsUnipackage(buildDir, { buildOfPath: packageDir });
} catch (e) {
// If we can't write to this directory, we don't get to cache our
// output, but otherwise life is good.
if (!(e && (e.code === 'EACCES' || e.code === 'EPERM')))
throw e;
}
}
});
}
@@ -272,7 +311,7 @@ _.extend(Library.prototype, {
// Register local package directories with a watchSet. We want to know if a
// package is created or deleted, which includes both its top-level source
// directory or its package.js file.
// directory and its main package metadata file.
watchLocalPackageDirs: function (watchSet) {
var self = this;
_.each(self.localPackageDirs, function (packageDir) {
@@ -283,7 +322,8 @@ _.extend(Library.prototype, {
_.each(packages, function (p) {
watch.readAndWatchFile(watchSet,
path.join(packageDir, p, 'package.js'));
// XXX unipackage.json too?
watch.readAndWatchFile(watchSet,
path.join(packageDir, p, 'unipackage.json'));
});
});
},

View File

@@ -638,9 +638,10 @@ Fiber(function () {
// Find upgraders (in order) necessary to upgrade the app for the new
// release (new metadata file formats, etc, or maybe even updating renamed
// APIs).
var oldManifest = warehouse.ensureReleaseExistsAndReturnManifest(
appRelease);
// APIs). (If this is a pre-engine app with no .meteor/release file, run
// all upgraders.)
var oldManifest = appRelease === null ? {}
: warehouse.ensureReleaseExistsAndReturnManifest(appRelease);
// We can only run upgrades from pinned apps.
if (oldManifest) {
var upgraders = _.difference(context.releaseManifest.upgraders || [],

View File

@@ -231,6 +231,12 @@ _.extend(Slice.prototype, {
var handler = !fileOptions.isAsset && self._getSourceHandler(ext);
var contents = watch.readAndWatchFile(self.watchSet, absPath);
if (contents === null) {
buildmessage.error("File not found: " + source.relPath);
// recover by ignoring
return;
}
if (! handler) {
// If we don't have an extension handler, serve this file as a
// static resource on the client, or ignore it on the server.
@@ -359,6 +365,9 @@ _.extend(Slice.prototype, {
packageName: self.pkg.name,
rootOutputPath: self.pkg.serveRoot,
arch: self.arch,
archMatches: function (pattern) {
return archinfo.matches(self.arch, pattern);
},
fileOptions: fileOptions,
declaredExports: _.pluck(self.declaredExports, 'name'),
read: function (n) {
@@ -1792,6 +1801,28 @@ _.extend(Package.prototype, {
var otherSliceRegExp =
(sliceName === "server" ? /^client\/$/ : /^server\/$/);
// The paths that we've called checkForInfiniteRecursion on.
var seenPaths = {};
// Used internally by fs.realpathSync as an optimization.
var realpathCache = {};
var checkForInfiniteRecursion = function (relDir) {
var absPath = path.join(self.sourceRoot, relDir);
try {
var realpath = fs.realpathSync(absPath, realpathCache);
} catch (e) {
if (!e || e.code !== 'ELOOP')
throw e;
// else leave realpath undefined
}
if (realpath === undefined || _.has(seenPaths, realpath)) {
buildmessage.error("Symlink cycle detected at " + relDir);
// recover by returning no files
return true;
}
seenPaths[realpath] = true;
return false;
};
// Read top-level subdirectories. Ignore subdirectories that have
// special handling.
var sourceDirectories = readAndWatchDirectory('', {
@@ -1800,13 +1831,17 @@ _.extend(Package.prototype, {
/^public\/$/, /^private\/$/,
otherSliceRegExp].concat(sourceExclude)
});
checkForInfiniteRecursion('');
// XXX avoid infinite recursion with bad symlinks
while (!_.isEmpty(sourceDirectories)) {
var dir = sourceDirectories.shift();
// remove trailing slash
dir = dir.substr(0, dir.length - 1);
if (checkForInfiniteRecursion(dir))
return []; // pretend we found no files
// Find source files in this directory.
Array.prototype.push.apply(sources, readAndWatchDirectory(dir, {
include: sourceInclude,
@@ -1845,7 +1880,6 @@ _.extend(Package.prototype, {
include: [new RegExp('^' + assetDir + '/$')]
});
// XXX avoid infinite recursion with bad symlinks
if (!_.isEmpty(assetDirs)) {
if (!_.isEqual(assetDirs, [assetDir + '/']))
throw new Error("Surprising assetDirs: " + JSON.stringify(assetDirs));
@@ -1855,6 +1889,9 @@ _.extend(Package.prototype, {
// remove trailing slash
dir = dir.substr(0, dir.length - 1);
if (checkForInfiniteRecursion(dir))
return []; // pretend we found no files
// Find asset files in this directory.
var assetsAndSubdirs = readAndWatchDirectory(dir, {
include: [/.?/],

View File

@@ -561,9 +561,10 @@ exports.run = function (context, options) {
Status.running = true;
var rootUrl = process.env.ROOT_URL || ('http://localhost:' + outerPort);
var rootUrl = process.env.ROOT_URL ||
('http://localhost:' + outerPort + '/');
if (firstRun) {
process.stdout.write("=> Meteor server running on: " + rootUrl + "/\n");
process.stdout.write("=> Meteor server running on: " + rootUrl + "\n");
firstRun = false;
lastThingThatPrintedWasRestartMessage = false;
} else {

View File

@@ -228,9 +228,12 @@ var readDirectory = function (options) {
// XXX Does the treatment of symlinks make sense?
var stats = fs.statSync(path.join(options.absPath, entry));
} catch (e) {
// Disappeared after the readdirSync (or a dangling symlink)? Eh, pretend
// it was never there in the first place.
return;
if (e && (e.code === 'ENOENT')) {
// Disappeared after the readdirSync (or a dangling symlink)? Eh,
// pretend it was never there in the first place.
return;
}
throw e;
}
// XXX if we're on windows, I guess it's possible for files to end with '/'.
if (stats.isDirectory())