diff --git a/History.md b/History.md
index 2821ddcdfa..c96bc8f5b1 100644
--- a/History.md
+++ b/History.md
@@ -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
diff --git a/docs/.meteor/release b/docs/.meteor/release
index 2e41fbd3cc..71be59a840 100644
--- a/docs/.meteor/release
+++ b/docs/.meteor/release
@@ -1 +1 @@
-0.6.5-rc2
+galaxy-6
diff --git a/docs/client/api.html b/docs/client/api.html
index 618d2f3a7a..7e83680985 100644
--- a/docs/client/api.html
+++ b/docs/client/api.html
@@ -2348,6 +2348,10 @@ Matches any value.
Matches a primitive of the given type.
{{/dtdd}}
+{{#dtdd "Match.Integer"}}
+Matches a signed 32-bit integer. Doesn't match `Infinity`, `-Infinity`, or `NaN`.
+{{/dtdd}}
+
{{#dtdd "[pattern]"}}
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 "Match.Optional(pattern)"}}
-Matches either `undefined` or something that matches *pattern*.
+{{#dtdd "Match.Optional(pattern)"}} 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 "Match.OneOf(pattern1, pattern2, ...)"}}
diff --git a/packages/check/match.js b/packages/check/match.js
index 1e6461fa7d..60783c6c97 100644
--- a/packages/check/match.js
+++ b/packages/check/match.js
@@ -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;
+};
+
diff --git a/packages/check/match_test.js b/packages/check/match_test.js
index 1253167883..2707e4aebd 100644
--- a/packages/check/match_test.js
+++ b/packages/check/match_test.js
@@ -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\"]");
+});
+
diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js
index dd955a5754..23ad68999b 100644
--- a/packages/ctl/ctl.js
+++ b/packages/ctl/ctl.js
@@ -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 {
diff --git a/packages/facebook/facebook_server.js b/packages/facebook/facebook_server.js
index 088f7eaf85..f9f7c1d696 100644
--- a/packages/facebook/facebook_server.js
+++ b/packages/facebook/facebook_server.js
@@ -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});
}
};
diff --git a/packages/github/github_server.js b/packages/github/github_server.js
index 669b51dd42..78e342f421 100644
--- a/packages/github/github_server.js
+++ b/packages/github/github_server.js
@@ -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});
}
};
diff --git a/packages/google/google_server.js b/packages/google/google_server.js
index eba4263e5b..27c98321bb 100644
--- a/packages/google/google_server.js
+++ b/packages/google/google_server.js
@@ -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});
}
};
diff --git a/packages/json/json_native.js b/packages/json/json_native.js
new file mode 100644
index 0000000000..bbb4e00f84
--- /dev/null
+++ b/packages/json/json_native.js
@@ -0,0 +1,3 @@
+// Do we already have a global JSON object? Export it as our JSON object.
+if (window.JSON)
+ JSON = window.JSON;
diff --git a/packages/json/package.js b/packages/json/package.js
index 87602a5998..abb6a2ec71 100644
--- a/packages/json/package.js
+++ b/packages/json/package.js
@@ -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');
});
diff --git a/packages/less/plugin/compile-less.js b/packages/less/plugin/compile-less.js
index cee7fc5e7e..41980738a8 100644
--- a/packages/less/plugin/compile-less.js
+++ b/packages/less/plugin/compile-less.js
@@ -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
diff --git a/packages/livedata/livedata_connection_tests.js b/packages/livedata/livedata_connection_tests.js
index 72d56d65cb..119a78792d 100644
--- a/packages/livedata/livedata_connection_tests.js
+++ b/packages/livedata/livedata_connection_tests.js
@@ -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",
diff --git a/packages/livedata/livedata_tests.js b/packages/livedata/livedata_tests.js
index 7ea0f656e8..3fd1cd0c87 100644
--- a/packages/livedata/livedata_tests.js
+++ b/packages/livedata/livedata_tests.js
@@ -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);
diff --git a/packages/livedata/sockjs-0.3.4.js b/packages/livedata/sockjs-0.3.4.js
index a19b0d29d1..a3a2f687eb 100644
--- a/packages/livedata/sockjs-0.3.4.js
+++ b/packages/livedata/sockjs-0.3.4.js
@@ -23,9 +23,10 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
+// 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
// [*] Including lib/index.js
// Public object
diff --git a/packages/livedata/stream_client_sockjs.js b/packages/livedata/stream_client_sockjs.js
index d8c4bfb810..8605e6275c 100644
--- a/packages/livedata/stream_client_sockjs.js
+++ b/packages/livedata/stream_client_sockjs.js
@@ -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;
}
diff --git a/packages/logging/logging.js b/packages/logging/logging.js
index e33361c11d..7c742f31e4 100644
--- a/packages/logging/logging.js
+++ b/packages/logging/logging.js
@@ -247,7 +247,7 @@ Log.format = function (obj, options) {
'-',
timeStamp,
utcOffsetStr,
- timeInexact ? '?' : ' ',
+ timeInexact ? '? ' : ' ',
appInfo,
sourceInfo,
stderrIndicator].join('');
diff --git a/packages/logging/logging_test.js b/packages/logging/logging_test.js
index 4305abf7e0..4e138b3ef1 100644
--- a/packages/logging/logging_test.js
+++ b/packages/logging/logging_test.js
@@ -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}),
diff --git a/packages/meetup/meetup_server.js b/packages/meetup/meetup_server.js
index cdd2b49a71..fe4aa80e8e 100644
--- a/packages/meetup/meetup_server.js
+++ b/packages/meetup/meetup_server.js
@@ -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});
}
};
diff --git a/packages/meteor/plugin/basic-file-types.js b/packages/meteor/plugin/basic-file-types.js
index 14141699a3..3c87328198 100644
--- a/packages/meteor/plugin/basic-file-types.js
+++ b/packages/meteor/plugin/basic-file-types.js
@@ -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;
}
diff --git a/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json b/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json
index a8aafc87d9..ea5601e289 100644
--- a/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json
+++ b/packages/mongo-livedata/.npm/package/npm-shrinkwrap.json
@@ -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"
diff --git a/packages/mongo-livedata/package.js b/packages/mongo-livedata/package.js
index adc83c3862..00ae205b58 100644
--- a/packages/mongo-livedata/package.js
+++ b/packages/mongo-livedata/package.js
@@ -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',
diff --git a/packages/oauth1/oauth1_binding.js b/packages/oauth1/oauth1_binding.js
index b3455e26a9..8047f387c6 100644
--- a/packages/oauth1/oauth1_binding.js
+++ b/packages/oauth1/oauth1_binding.js
@@ -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});
}
};
diff --git a/packages/stylus/package.js b/packages/stylus/package.js
index 95e74bac9f..850d7cd49a 100644
--- a/packages/stylus/package.js
+++ b/packages/stylus/package.js
@@ -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');
});
diff --git a/packages/stylus/plugin/compile-stylus.js b/packages/stylus/plugin/compile-stylus.js
index 74b786d81d..7ddc33dcf5 100644
--- a/packages/stylus/plugin/compile-stylus.js
+++ b/packages/stylus/plugin/compile-stylus.js
@@ -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
+ });
+});
diff --git a/packages/test-in-console/driver.js b/packages/test-in-console/driver.js
index cbbd26b354..50e03156f3 100644
--- a/packages/test-in-console/driver.js
+++ b/packages/test-in-console/driver.js
@@ -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";
diff --git a/packages/universal-events/package.js b/packages/universal-events/package.js
index 55585255e7..3ce9c2f1ea 100644
--- a/packages/universal-events/package.js
+++ b/packages/universal-events/package.js
@@ -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',
diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js
index 651cd66f95..5f085dfb14 100644
--- a/packages/webapp/webapp_server.js
+++ b/packages/webapp/webapp_server.js
@@ -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();
diff --git a/packages/weibo/weibo_server.js b/packages/weibo/weibo_server.js
index c5e11cd475..1d2f525638 100644
--- a/packages/weibo/weibo_server.js
+++ b/packages/weibo/weibo_server.js
@@ -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});
}
};
diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js
index e147361712..20ac3dfd87 100644
--- a/tools/deploy-galaxy.js
+++ b/tools/deploy-galaxy.js
@@ -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
diff --git a/tools/library.js b/tools/library.js
index fc9c039c55..4238bce28b 100644
--- a/tools/library.js
+++ b/tools/library.js
@@ -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'));
});
});
},
diff --git a/tools/meteor.js b/tools/meteor.js
index df708d31df..290729f6b7 100644
--- a/tools/meteor.js
+++ b/tools/meteor.js
@@ -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 || [],
diff --git a/tools/packages.js b/tools/packages.js
index 7ee8dc248a..707efade99 100644
--- a/tools/packages.js
+++ b/tools/packages.js
@@ -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: [/.?/],
diff --git a/tools/run.js b/tools/run.js
index 6982cb9f2a..832d68c226 100644
--- a/tools/run.js
+++ b/tools/run.js
@@ -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 {
diff --git a/tools/watch.js b/tools/watch.js
index d4b05bdb37..ea685773cb 100644
--- a/tools/watch.js
+++ b/tools/watch.js
@@ -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())