From 7db4b8eaf0fb6d67f335f4b7f9aa19652a7d7564 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 1 Aug 2013 17:42:20 -0700 Subject: [PATCH 01/42] Check's error has path in the object tree. --- packages/check/match.js | 44 +++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index 1e6461fa7d..52474979b9 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.path) + err.message += "\nPath: " + err.path; + throw err; + } }; Match = { @@ -29,10 +34,11 @@ Match = { return new ObjectIncluding(pattern); }, - // 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; + // Path of the value in the object tree. Eg: "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"); @@ -132,8 +138,14 @@ 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 = "[" + index + "]." + err.path; + throw err; + } }); return; } @@ -206,14 +218,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 '" + key + "'"); + } + } catch (err) { + if (err instanceof Match.Error) + err.path = key + "." + err.path; + throw err; } }); From 9c3cd308517efe7ac86cbabca861dea46c7286fb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 18:12:27 -0700 Subject: [PATCH 02/42] initial rough draft for HISTORY for 0.6.5 --- History.md | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2821ddcdfa..9c3a62d71e 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,13 @@ ## vNEXT +* 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 +17,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 +42,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 From 21bff7415dfae70bfc05848b4f1b1e2ca072e9ee Mon Sep 17 00:00:00 2001 From: David Glasser Date: Thu, 1 Aug 2013 18:12:55 -0700 Subject: [PATCH 03/42] Working on 0.6.5. --- History.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/History.md b/History.md index 9c3a62d71e..c96bc8f5b1 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,8 @@ ## 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 From 1cd8931eff11e6393086af76a44dd2e39a9de21f Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Thu, 1 Aug 2013 23:56:33 -0700 Subject: [PATCH 04/42] Nick's comments. One line Error message, comments. --- packages/check/match.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index 52474979b9..36c5be5b46 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -13,8 +13,9 @@ check = function (value, pattern) { try { checkSubtree(value, pattern); } catch (err) { - if (err.path) - err.message += "\nPath: " + err.path; + if ((err instanceof Match.Error) && err.path) + err.message += " in field " + err.path.replace(/\.\[/g, "[") + .replace(/.$/, ""); throw err; } }; @@ -38,6 +39,7 @@ Match = { Error: Meteor.makeErrorType("Match.Error", function (msg) { this.message = "Match error: " + msg; // Path of the value in the object tree. Eg: "vals[3].entity.created" + // Initially is empty and gets populated on the way up the recursion stack. 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. From 392daa0a995bdb3d88c6b476d058bbb3596d7bd1 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 10:59:41 -0700 Subject: [PATCH 05/42] Detect symlink cycles in app dirs. --- tools/packages.js | 32 ++++++++++++++++++++++++++++++-- tools/watch.js | 9 ++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/tools/packages.js b/tools/packages.js index 7ee8dc248a..8fd844dbd1 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -1792,6 +1792,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 +1822,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 +1871,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 +1880,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/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()) From af5efd29963906b18c690e0a35168911ed2c9aac Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 11:18:31 -0700 Subject: [PATCH 06/42] Better error (with watching!) for missing source files. --- tools/packages.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/packages.js b/tools/packages.js index 8fd844dbd1..baa536c19b 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. From e712d78cfb5984a29a22d56279ceffdfa17d7982 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 11:19:08 -0700 Subject: [PATCH 07/42] Export JSON from json package. Make sure to use the built-in JSON if it exists. Fixes #1204. --- packages/json/json_native.js | 3 +++ packages/json/package.js | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 packages/json/json_native.js 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'); }); From 8e4be18c8838dc2f34189b4d120516c5d38e56e2 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 11:21:55 -0700 Subject: [PATCH 08/42] Use json package in sockjs client. Because sockjs-client doesn't think it's loading in a closure, it ALWAYS ends up redefining JSON to non-native code, and sockjs-client does not want to change this: https://github.com/sockjs/sockjs-client/issues/123 Fixes #1162. --- packages/livedata/sockjs-0.3.4.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 From 558bcb8877573cf832203972fea749f18ff4f851 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 11:40:55 -0700 Subject: [PATCH 09/42] Don't print a doubled slash if it's included in ROOT_URL. This should not affect how Meteor.absoluteUrl actually works since it adds a slash to the end of the root URL if it doesn't have one already. --- tools/run.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 { From 0e785039ae30161ccdaa601ffcf3919c5f14731c Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 11:47:59 -0700 Subject: [PATCH 10/42] Avoid using Meteor.absoluteUrl for client-side tests. This way tests can pass both when ran against localhost or against a 10.0.2.2-style hostname for VMs. --- packages/livedata/livedata_tests.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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); From ec05981a1a76e3f76ed58b8e06f1227846f1e3a1 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 12:37:44 -0700 Subject: [PATCH 11/42] Improve comment. Cleaner way to construct path string. --- packages/check/match.js | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index 36c5be5b46..daf18d2175 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -14,8 +14,7 @@ check = function (value, pattern) { checkSubtree(value, pattern); } catch (err) { if ((err instanceof Match.Error) && err.path) - err.message += " in field " + err.path.replace(/\.\[/g, "[") - .replace(/.$/, ""); + err.message += " in field " + err.path; throw err; } }; @@ -38,8 +37,10 @@ Match = { // XXX matchers should know how to describe themselves for errors Error: Meteor.makeErrorType("Match.Error", function (msg) { this.message = "Match error: " + msg; - // Path of the value in the object tree. Eg: "vals[3].entity.created" - // Initially is empty and gets populated on the way up the recursion stack. + // 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. @@ -144,8 +145,12 @@ var checkSubtree = function (value, pattern) { try { checkSubtree(valueElement, pattern[0]); } catch (err) { - if (err instanceof Match.Error) - err.path = "[" + index + "]." + err.path; + if (err instanceof Match.Error) { + if (err.path && err.path[0] !== '[') + err.path = "[" + index + "]." + err.path; + else + err.path = "[" + index + "]" + err.path; + } throw err; } }); @@ -231,8 +236,12 @@ var checkSubtree = function (value, pattern) { throw new Match.Error("Unknown key '" + key + "'"); } } catch (err) { - if (err instanceof Match.Error) - err.path = key + "." + err.path; + if (err instanceof Match.Error) { + if (err.path && err.path[0] !== "[") + err.path = key + "." + err.path; + else + err.path = key + err.path; + } throw err; } }); From cd3d87625f7ddc2cc8f9d474576bbc03514e2ce3 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 12:41:29 -0700 Subject: [PATCH 12/42] Remove duplication in error of unknown key. "Unknown key in field Y" instead of "Unknown key Y in field Y" --- packages/check/match.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/check/match.js b/packages/check/match.js index daf18d2175..2ea698eb0a 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -233,7 +233,7 @@ var checkSubtree = function (value, pattern) { checkSubtree(subValue, optionalPatterns[key]); } else { if (!unknownKeysAllowed) - throw new Match.Error("Unknown key '" + key + "'"); + throw new Match.Error("Unknown key"); } } catch (err) { if (err instanceof Match.Error) { From aa6315e1092f2af406829812fc6d43b460ea1300 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 13:46:54 -0700 Subject: [PATCH 13/42] minor improvements to a livedata test --- .../livedata/livedata_connection_tests.js | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) 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", From 6e67c755e176428d4fcc29042feced511c7eea77 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 13:47:10 -0700 Subject: [PATCH 14/42] clear all sockjs handlers --- packages/livedata/stream_client_sockjs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From 1023a97e07d70f436a4fba41c42b4bb66073180e Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 14:15:34 -0700 Subject: [PATCH 15/42] Add Match.Integer match for 32-bit integers. #1171 --- packages/check/match.js | 16 ++++++++++++++++ packages/check/match_test.js | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/packages/check/match.js b/packages/check/match.js index 2ea698eb0a..cf6b364cc3 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -33,6 +33,14 @@ Match = { ObjectIncluding: function (pattern) { return new ObjectIncluding(pattern); }, + // Matches only signed 32-bit integers + // 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 by JavaScript specs. + Integer: ['__integer__'], // XXX matchers should know how to describe themselves for errors Error: Meteor.makeErrorType("Match.Error", function (msg) { @@ -128,6 +136,14 @@ 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) { + if (typeof value !== "number" || (value | 0) !== value) + throw new Match.Error("Expected Integer, got " + + (value instanceof Object ? EJSON.stringify(value) : value)); + return; + } + // "Object" is shorthand for Match.ObjectIncluding({}); if (pattern === Object) pattern = Match.ObjectIncluding({}); diff --git a/packages/check/match_test.js b/packages/check/match_test.js index 1253167883..ea6465ce5a 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]); From 86022fb06eae0a89500a92c6f55126e3162a66cd Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 14:20:38 -0700 Subject: [PATCH 16/42] Docs for Match.Integer --- docs/client/api.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/client/api.html b/docs/client/api.html index 618d2f3a7a..128cb20048 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -2344,10 +2344,15 @@ The following patterns can be used as pattern arguments to `check` and `Match.te Matches any value. {{/dtdd}} +{{#dtdd "Match.Integer"}} +Matches signed 32-bit integer. +{{/dtdd}} + {{#dtdd "String, Number, Boolean, undefined, null"}} Matches a primitive of the given type. {{/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; From ffc31748f5dd2e81e475889c440f9ce2ddd8c996 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 14:20:59 -0700 Subject: [PATCH 17/42] Make docs for Match.Optional more truthy about undefined in an object. --- docs/client/api.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/client/api.html b/docs/client/api.html index 128cb20048..63c3eb670c 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -2377,7 +2377,7 @@ Matches any plain Object with any keys; equivalent to {{/dtdd}} {{#dtdd "Match.Optional(pattern)"}} -Matches either `undefined` or something that matches *pattern*. +Matches either `undefined` on the top level or absence in an object or something that matches *pattern*. {{/dtdd}} {{#dtdd "Match.OneOf(pattern1, pattern2, ...)"}} From 9deb7bc94447f341e3c416ded793d4e1542cfa99 Mon Sep 17 00:00:00 2001 From: David Greenspan Date: Fri, 2 Aug 2013 14:29:36 -0700 Subject: [PATCH 18/42] make LESS error messages work --- packages/less/plugin/compile-less.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/less/plugin/compile-less.js b/packages/less/plugin/compile-less.js index cee7fc5e7e..a860ad2861 100644 --- a/packages/less/plugin/compile-less.js +++ b/packages/less/plugin/compile-less.js @@ -29,7 +29,7 @@ Plugin.registerSourceHandler("less", function (compileStep) { // 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); + throw new Error(compileStep.inputPath + ": Less compiler error: " + e.message); } });; From c22f681d9ce3400601d799838655718b92de1ccb Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 14:57:30 -0700 Subject: [PATCH 19/42] Fix a test in IE8; elsewhere make it actually test what it's trying to test. --- packages/logging/logging_test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/logging/logging_test.js b/packages/logging/logging_test.js index 4305abf7e0..679af58e40 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,11 @@ Tinytest.add("logging - log", function (test) { var recieved = intercepted[index]; var obj = EJSON.parse(recieved); - if (_.isDate(expected)) + // IE8 doesn't support this date format. Skip it. + if (expected && expected.toString && expected.toString() === "NaN") + return; + + if (_.isDate(testcase[0])) obj.message = new Date(obj.message); test.equal(obj.message, expected, 'Logging ' + testName); }); From c3d1f7bbd221e2877c35b050ce526583ae2ff667 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 15:16:53 -0700 Subject: [PATCH 20/42] Don't fail if stylus or less files are in a non-client-specific place. Make the plugin's arch check less sketchy. Improve less and stylus error handling. --- packages/less/plugin/compile-less.js | 38 +++++++++++++++------- packages/meteor/plugin/basic-file-types.js | 7 ++-- packages/stylus/package.js | 2 +- packages/stylus/plugin/compile-stylus.js | 33 +++++++++++++------ tools/packages.js | 3 ++ 5 files changed, 57 insertions(+), 26 deletions(-) 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/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/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/tools/packages.js b/tools/packages.js index baa536c19b..707efade99 100644 --- a/tools/packages.js +++ b/tools/packages.js @@ -365,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) { From 67129c7e7076b93b81b7393ed840fee23319d3ad Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Fri, 2 Aug 2013 15:22:49 -0700 Subject: [PATCH 21/42] Nick's comments. Don't duplicate the logic. --- packages/check/match.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index cf6b364cc3..ace58b63f7 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -163,9 +163,8 @@ var checkSubtree = function (value, pattern) { } catch (err) { if (err instanceof Match.Error) { if (err.path && err.path[0] !== '[') - err.path = "[" + index + "]." + err.path; - else - err.path = "[" + index + "]" + err.path; + err.path = "." + err.path; + err.path = "[" + index + "]" + err.path; } throw err; } @@ -254,9 +253,8 @@ var checkSubtree = function (value, pattern) { } catch (err) { if (err instanceof Match.Error) { if (err.path && err.path[0] !== "[") - err.path = key + "." + err.path; - else - err.path = key + err.path; + err.path = "." + err.path; + err.path = key + err.path; } throw err; } From 063c4462635541c49d85a09236a2e8a8548127b1 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 16:29:10 -0700 Subject: [PATCH 22/42] Put a space after the "this timestamp isn't actually from the Log call" question mark. --- packages/logging/logging.js | 2 +- packages/logging/logging_test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 679af58e40..9fa50f0099 100644 --- a/packages/logging/logging_test.js +++ b/packages/logging/logging_test.js @@ -141,7 +141,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}), From 5bd85ccc3770c0c5aba70f57b8af8c30108db052 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 16:53:55 -0700 Subject: [PATCH 23/42] Make support for unipackages in local package dirs explicit, but scary. --- tools/library.js | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tools/library.js b/tools/library.js index fc9c039c55..27ff877e03 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); @@ -272,7 +290,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 +301,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')); }); }); }, From c1f8c7f0e52cdbce74d867818f7985a864af5225 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 2 Aug 2013 17:09:43 -0700 Subject: [PATCH 24/42] Don't crash if we lack permission to write out .build. --- tools/library.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tools/library.js b/tools/library.js index 27ff877e03..6d174ba215 100644 --- a/tools/library.js +++ b/tools/library.js @@ -242,8 +242,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; + } } }); } From 3a267f1fdca0b4978af6ae077f0c54e05ea2084e Mon Sep 17 00:00:00 2001 From: David Glasser Date: Sat, 3 Aug 2013 23:04:16 -0400 Subject: [PATCH 25/42] Fix error on upgrading from pre-engine app. Fixes #1280. --- tools/meteor.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 || [], From 580de65f62a76a550e8deb261df0816f7a515f72 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 5 Aug 2013 13:26:43 -0700 Subject: [PATCH 26/42] Grammar corrections. Comments moving. --- docs/client/api.html | 2 +- packages/check/match.js | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 63c3eb670c..1001b49cb0 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -2345,7 +2345,7 @@ Matches any value. {{/dtdd}} {{#dtdd "Match.Integer"}} -Matches signed 32-bit integer. +Matches a signed 32-bit integer. {{/dtdd}} {{#dtdd "String, Number, Boolean, undefined, null"}} diff --git a/packages/check/match.js b/packages/check/match.js index ace58b63f7..90c9c7df1c 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -34,12 +34,6 @@ Match = { return new ObjectIncluding(pattern); }, // Matches only signed 32-bit integers - // 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 by JavaScript specs. Integer: ['__integer__'], // XXX matchers should know how to describe themselves for errors @@ -138,10 +132,16 @@ var checkSubtree = function (value, pattern) { // Match.Integer is special type encoded with array if (pattern === Match.Integer) { - if (typeof value !== "number" || (value | 0) !== value) - throw new Match.Error("Expected Integer, got " - + (value instanceof Object ? EJSON.stringify(value) : value)); - return; + // 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({}); From e16738e92004e04086f4baea3fa007a8123843df Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 5 Aug 2013 16:14:59 -0700 Subject: [PATCH 27/42] Better docs for Match.Optional. --- docs/client/api.html | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/client/api.html b/docs/client/api.html index 1001b49cb0..7e83680985 100644 --- a/docs/client/api.html +++ b/docs/client/api.html @@ -2344,14 +2344,13 @@ The following patterns can be used as pattern arguments to `check` and `Match.te Matches any value. {{/dtdd}} -{{#dtdd "Match.Integer"}} -Matches a signed 32-bit integer. -{{/dtdd}} - {{#dtdd "String, Number, Boolean, undefined, null"}} 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 @@ -2376,8 +2375,19 @@ Matches any plain Object with any keys; equivalent to `Match.ObjectIncluding({})`. {{/dtdd}} -{{#dtdd "Match.Optional(pattern)"}} -Matches either `undefined` on the top level or absence in an object 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, ...)"}} From 723e4b5b505b8fedf6030ae28d47a81fcfd36781 Mon Sep 17 00:00:00 2001 From: Tim Haines Date: Fri, 26 Jul 2013 18:17:39 -0700 Subject: [PATCH 28/42] Attach response object to accounts generated errors resulting from HTTP failures --- packages/facebook/facebook_server.js | 6 ++++-- packages/github/github_server.js | 6 ++++-- packages/google/google_server.js | 6 ++++-- packages/meetup/meetup_server.js | 6 ++++-- packages/oauth1/oauth1_binding.js | 3 ++- packages/weibo/weibo_server.js | 6 ++++-- 6 files changed, 22 insertions(+), 11 deletions(-) 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/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/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/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}); } }; From 017407301dade46d8412bab2a60419e3eb4c6bfb Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 6 Aug 2013 11:59:06 -0700 Subject: [PATCH 29/42] Also skip logging test on old safari that doesn't support it. --- packages/logging/logging_test.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/logging/logging_test.js b/packages/logging/logging_test.js index 9fa50f0099..4e138b3ef1 100644 --- a/packages/logging/logging_test.js +++ b/packages/logging/logging_test.js @@ -72,8 +72,10 @@ Tinytest.add("logging - log", function (test) { var recieved = intercepted[index]; var obj = EJSON.parse(recieved); - // IE8 doesn't support this date format. Skip it. - if (expected && expected.toString && expected.toString() === "NaN") + // 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])) From d70c3d54e03ac7d8ef64e706cdf83507c39770f9 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 6 Aug 2013 12:16:11 -0700 Subject: [PATCH 30/42] Use soon-to-be-released mongo driver version 1.3.16. It includes a fix for a connection storm that impacted production hosting. --- packages/mongo-livedata/.npm/package/npm-shrinkwrap.json | 4 ++-- packages/mongo-livedata/package.js | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) 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', From 6e565bf26e13c25b0279274f5fbed90970a4db5d Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 6 Aug 2013 12:53:33 -0700 Subject: [PATCH 31/42] Add missing package usage. Fixes #1284. --- packages/universal-events/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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', From 6decfd43d628b8d29c9c43245270f8c0f3065767 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Fri, 12 Jul 2013 23:37:08 -0700 Subject: [PATCH 32/42] Use setting to select which proxy service to bind to --- packages/ctl/ctl.js | 3 ++- packages/webapp/webapp_server.js | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index dd955a5754..ee1f6a9108 100644 --- a/packages/ctl/ctl.js +++ b/packages/ctl/ctl.js @@ -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 diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index 651cd66f95..d167942557 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, From 9d577ae58c9424c4d698d11229cbc31f24240940 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 5 Aug 2013 19:51:17 -0700 Subject: [PATCH 33/42] Specify securePort for admin apps. Otherwise they bind to 4433 by default, which conflicts with normal proxy running on the same machine. --- packages/ctl/ctl.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/ctl/ctl.js b/packages/ctl/ctl.js index ee1f6a9108..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 @@ -95,7 +95,8 @@ Ctl.Commands.push({ bindEnv: "PORT", routeEnv: "ROUTE" } - } + }, + tags: ["runner"] }]); console.log("Started a server."); } else { From e7bb166a02152ec07040ecd7e345274f6406e461 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 6 Aug 2013 15:23:57 -0700 Subject: [PATCH 34/42] Check for invalid package names early in the codepath. We already don't work with packages that contain '.' in the name, this just moves the error up and makes it clearer. --- tools/library.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tools/library.js b/tools/library.js index 6d174ba215..5db0d12700 100644 --- a/tools/library.js +++ b/tools/library.js @@ -175,6 +175,17 @@ _.extend(Library.prototype, { return self.loadedPackages[name].pkg; } + // Check for invalid package names. + // + // XXX should we be even stricter and whitelist something like + // /\-_A-Za-z0-9/ instead of blacklisting some special characters? + // What about unicode package names? + if (/[\.\?|'"#<>\(\)]/.test(name)) { + if (throwOnError === false) + return null; + throw new Error("Invalid package name: " + name); + } + var packageDir = self.findPackageDirectory(name); if (! packageDir) { From a9a99ceafd60687cb061c00a87cbadb43a84dd32 Mon Sep 17 00:00:00 2001 From: Nick Martin Date: Tue, 6 Aug 2013 15:55:06 -0700 Subject: [PATCH 35/42] followon to previous commit: actually use whitelist instead of blacklist for package name contents. --- tools/library.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tools/library.js b/tools/library.js index 5db0d12700..4238bce28b 100644 --- a/tools/library.js +++ b/tools/library.js @@ -175,12 +175,15 @@ _.extend(Library.prototype, { return self.loadedPackages[name].pkg; } - // Check for invalid package names. + // 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. // - // XXX should we be even stricter and whitelist something like - // /\-_A-Za-z0-9/ instead of blacklisting some special characters? - // What about unicode package names? - if (/[\.\?|'"#<>\(\)]/.test(name)) { + // 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); From 63327b507587dd00fc7b7c650521f028accf83fb Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 7 Aug 2013 13:41:22 -0700 Subject: [PATCH 36/42] Use callbacks for proxy binding method calls. If an app is trying to connect to a proxy that's down, this prevents it from freezing up and allows it to try another proxy if it hears about one. --- packages/webapp/webapp_server.js | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/webapp/webapp_server.js b/packages/webapp/webapp_server.js index d167942557..5f085dfb14 100644 --- a/packages/webapp/webapp_server.js +++ b/packages/webapp/webapp_server.js @@ -521,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, @@ -529,7 +552,7 @@ WebAppInternals.bindToProxy = function (proxyConfig) { port: port, pathPrefix: bindPathPrefix + '/websocket' } - }); + }, bindingDoneCallback("ddp")); proxy.call('bindHttp', { pid: pid, bindTo: { @@ -542,7 +565,7 @@ WebAppInternals.bindToProxy = function (proxyConfig) { port: port, pathPrefix: bindPathPrefix } - }); + }, bindingDoneCallback("http")); if (proxyConfig.securePort !== null) { proxy.call('bindHttp', { pid: pid, @@ -557,9 +580,8 @@ WebAppInternals.bindToProxy = function (proxyConfig) { port: port, pathPrefix: bindPathPrefix } - }); + }, bindingDoneCallback("https")); } - Log("Bound to proxy"); }; runWebAppServer(); From b7d810cbc6688d2b49e6c9facfd4a1d022901fc1 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 7 Aug 2013 15:05:00 -0700 Subject: [PATCH 37/42] Bump docs release --- docs/.meteor/release | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ca067ad816856e2ac3efbd4ca3e62822b0b7a371 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Wed, 7 Aug 2013 18:38:57 -0700 Subject: [PATCH 38/42] Connect to galaxy for deploys only after bundling is done. The connection was timing out during the bundle process (the setTimeout callback was being enqueued before the connect event fired). --- tools/deploy-galaxy.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index e147361712..5d13580a34 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -130,9 +130,6 @@ 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); @@ -166,6 +163,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 From afa7cb6159cef2f071f72bda52bdb14c159c1d72 Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 12 Aug 2013 12:20:23 -0700 Subject: [PATCH 39/42] Log exceptions on tests run with test-in-console. Also counts exceptions as failures and removes unused finished var. --- packages/test-in-console/driver.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) 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"; From cd8248cb33163954dd5af674d022b7f476e2b8cb Mon Sep 17 00:00:00 2001 From: Emily Stark Date: Mon, 12 Aug 2013 14:35:29 -0700 Subject: [PATCH 40/42] Comment about why we don't connect to galaxy before bundling --- tools/deploy-galaxy.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tools/deploy-galaxy.js b/tools/deploy-galaxy.js index 5d13580a34..20ac3dfd87 100644 --- a/tools/deploy-galaxy.js +++ b/tools/deploy-galaxy.js @@ -136,6 +136,12 @@ exports.deploy = function (options) { 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, From 2e7845af8024a7c64270f27fff9156e982de34bb Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 12 Aug 2013 15:31:07 -0700 Subject: [PATCH 41/42] Path of error is escaped where needed and looks more like actual JS. - Escape keys looking like something complex - Check simple keys with simple regex, full story: http://stackoverflow.com/questions/1661197/valid-characters-for-javascript-variable-names - Tests --- packages/check/match.js | 33 +++++++++++++++++++++++-------- packages/check/match_test.js | 38 ++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 8 deletions(-) diff --git a/packages/check/match.js b/packages/check/match.js index 90c9c7df1c..60783c6c97 100644 --- a/packages/check/match.js +++ b/packages/check/match.js @@ -162,9 +162,7 @@ var checkSubtree = function (value, pattern) { checkSubtree(valueElement, pattern[0]); } catch (err) { if (err instanceof Match.Error) { - if (err.path && err.path[0] !== '[') - err.path = "." + err.path; - err.path = "[" + index + "]" + err.path; + err.path = _prependPath(index, err.path); } throw err; } @@ -251,11 +249,8 @@ var checkSubtree = function (value, pattern) { throw new Match.Error("Unknown key"); } } catch (err) { - if (err instanceof Match.Error) { - if (err.path && err.path[0] !== "[") - err.path = "." + err.path; - err.path = key + err.path; - } + if (err instanceof Match.Error) + err.path = _prependPath(key, err.path); throw err; } }); @@ -309,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 ea6465ce5a..bf807e6e4f 100644 --- a/packages/check/match_test.js +++ b/packages/check/match_test.js @@ -217,3 +217,41 @@ 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) { + 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\"]"); +}); + From d9da7b8f0337fb740e1f0d7f1e1ee8a809f809f5 Mon Sep 17 00:00:00 2001 From: Slava Kim Date: Mon, 12 Aug 2013 23:30:26 -0700 Subject: [PATCH 42/42] Fix check test failure for FF 3.6 TIL sometimes JSON.stringify can convert new lines to "\u000a" as supposed to "\n".# Please enter the commit message for your changes. Lines starting --- packages/check/match_test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/check/match_test.js b/packages/check/match_test.js index bf807e6e4f..2707e4aebd 100644 --- a/packages/check/match_test.js +++ b/packages/check/match_test.js @@ -223,6 +223,8 @@ Tinytest.add("check - Match error path", function (test) { 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",