From 7630fb7a357f18e366d364cd695bec4405fa4c0b Mon Sep 17 00:00:00 2001 From: skirunman Date: Thu, 13 Jul 2017 17:47:12 -0700 Subject: [PATCH 01/53] Update to MongoDB v3.2.15 Install latest version of MongoDB 3.2.x by default. --- scripts/build-dev-bundle-common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/build-dev-bundle-common.sh b/scripts/build-dev-bundle-common.sh index 5b0b7e651f..de6be8bece 100644 --- a/scripts/build-dev-bundle-common.sh +++ b/scripts/build-dev-bundle-common.sh @@ -5,7 +5,7 @@ set -u UNAME=$(uname) ARCH=$(uname -m) -MONGO_VERSION=3.2.12 +MONGO_VERSION=3.2.15 NODE_VERSION=4.8.4 NPM_VERSION=4.6.1 From 3f66a85c46f36fb79306b5b6cb70901cca2b8333 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 14 Jul 2017 00:24:00 -0700 Subject: [PATCH 02/53] [test] Ensure Galaxy deploy login works properly. This implements a non-`galaxy` labeled test (which will run with normal CI tests) which tests that Galaxy login both fails and succeeds properly much in the same way that our existing auth tests run except for the `meteor deploy` command. --- tools/tests/login.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tools/tests/login.js b/tools/tests/login.js index 3c3e9437de..82b678d834 100644 --- a/tools/tests/login.js +++ b/tools/tests/login.js @@ -122,3 +122,34 @@ selftest.define("login", ['net'], function () { run.matchErr("Login failed"); run.expectExit(1); }); + +// This is a Galaxy-related command (deploy), but still pretty auth-y. +selftest.define("login on deploy", ['net'], function () { + const s = new Sandbox; + + const appName = testUtils.randomAppName(); + + s.createApp(appName, "standard-app"); + s.cd(appName); + + let run = s.run("deploy", appName); + run.matchErr(/You must be logged in to deploy/); + + run.matchErr("Email:"); + run.write("test@test.com\n"); + + run.matchErr("Logging in as test."); + + run.matchErr("Password:"); + run.write("SoVeryWrong\n"); + run.waitSecs(commandTimeoutSecs); + run.matchErr("Login failed"); + + run.matchErr("Password:"); + run.write("testtest\n"); + run.waitSecs(commandTimeoutSecs); + run.match("Talking to Galaxy servers"); + + // "test" user can't actually deploy, so it will still fail. + run.expectExit(1); +}); From 661b6f74e98e3163d23020e6c5f4f1f0c71f7ba7 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 13 Jul 2017 14:40:31 -0700 Subject: [PATCH 03/53] Clear password between retries when auth has failed. --- tools/meteor-services/auth.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/meteor-services/auth.js b/tools/meteor-services/auth.js index 2d2c043a6e..3660f42934 100644 --- a/tools/meteor-services/auth.js +++ b/tools/meteor-services/auth.js @@ -508,6 +508,7 @@ var doInteractivePasswordLogin = function (options) { } else { loginFailed(); if (options.retry) { + delete loginData.password; Console.error(); continue; } else { From f3440c9a9cfb03468627cd3c9b87eb934f77cf35 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Tue, 25 Jul 2017 08:19:19 -0400 Subject: [PATCH 04/53] Adjust the previous watcher check to handle moved files. When checking the `entriesByIno` Map to see if an `entry` already exists for the specified inode, also check to make sure the found `entry` is only re-used if the current file watcher path matches the returned path. This makes sure new file watchers are created for moved files (so files with the same inode), instead of attempting to re-use a file watcher that's watching an invalid path. --- tools/fs/safe-watcher.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/fs/safe-watcher.js b/tools/fs/safe-watcher.js index 3024a239e3..3793a43034 100644 --- a/tools/fs/safe-watcher.js +++ b/tools/fs/safe-watcher.js @@ -78,7 +78,10 @@ function startNewWatcher(absPath) { const stat = statOrNull(absPath); const ino = stat && stat.ino; if (ino > 0 && entriesByIno.has(ino)) { - return entriesByIno.get(ino); + const entry = entriesByIno.get(ino); + if (entries[absPath] === entry) { + return entry; + } } function safeUnwatch() { From 170de0c11878e965b4a52560840401d103dcaa3a Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Wed, 26 Jul 2017 00:37:21 +0300 Subject: [PATCH 05/53] Change `bundle-visualizer` README to instruct `--extra-packages`. The suggestion to `meteor add` and then `meteor remove` is no longer relevant with the addition of the awesome new `--extra-packages` option from @mpowaga in meteor/meteor#8769. --- packages/non-core/bundle-visualizer/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/non-core/bundle-visualizer/README.md b/packages/non-core/bundle-visualizer/README.md index 5c9b7c25ca..23bed7f9b3 100644 --- a/packages/non-core/bundle-visualizer/README.md +++ b/packages/non-core/bundle-visualizer/README.md @@ -35,13 +35,13 @@ this package must be used in conjunction with the `--production` flag to the `meteor` tool to simulate production bundling and enable minification. > **IMPORTANT:** Since this package is active in production mode, it is critical -> to remove this package prior to bundling or deploying the application. +> to only add this package temporarily. This can be easily accomplished using +> the `--extra-packages` option to `meteor`. ### Enabling ```sh $ cd app/ -$ meteor add bundle-visualizer -$ meteor --production +$ meteor --extra-packages bundle-visualizer --production ``` ### Viewing @@ -52,9 +52,9 @@ application. ### Disabling -> It is important to remove this package prior to bundling or deploying to -> production. +If you used `--extra-packages`, simply remove `bundle-visualizer` from the list +of included packages and run `meteor` as normal. -```sh -$ meteor remove bundle-visualizer -``` \ No newline at end of file +> If you've added `bundle-visualizer` permanently with `meteor add`, it is +> important to remove this package prior to bundling or deploying to +> production with `meteor remove `bundle-visualizer`. From 4d37a05fb33576b8f167168f4bc1b12821354615 Mon Sep 17 00:00:00 2001 From: Simon Fridlund Date: Wed, 26 Jul 2017 17:08:00 +0200 Subject: [PATCH 06/53] Add mongo-dev-server package (#8853) * Add mongo-dev-server package Only start the MongoDB server if this package is present in the project. * Small layout/formatting adjustments; updated README. * Allow tests using fake-mongod to start (fake) Mongo. * Adjust test stdout matching to be less sensitive to ordering. * Add `mongo-dev-server` History.md entry. * Remove mongo start check since the tested for error prevents mongo startup. * Remove README traling whitespace. * Bump mongo package version. --- History.md | 12 ++++++++++++ packages/mongo-dev-server/README.md | 18 ++++++++++++++++++ packages/mongo-dev-server/package.js | 12 ++++++++++++ packages/mongo-dev-server/server.js | 3 +++ packages/mongo/package.js | 5 +++-- tools/runners/run-all.js | 19 ++++++++++++++++++- tools/tests/compiler-plugins.js | 5 +++-- tools/tests/static-html.js | 2 +- 8 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 packages/mongo-dev-server/README.md create mode 100644 packages/mongo-dev-server/package.js create mode 100644 packages/mongo-dev-server/server.js diff --git a/History.md b/History.md index e5e8b4abe0..087aae7f0c 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,17 @@ ## v.NEXT +* A new package called `mongo-dev-server` has been created and wired into + `mongo` as a dependency. As long as this package is included in a Meteor + application (which it is by default since all new Meteor apps have `mongo` + as a dependency), a local development MongoDB server is started alongside + the application. This package was created to provide a way to disable the + local development Mongo server, when `mongo` isn't needed (e.g. when using + Meteor as a build system only). If an application has no dependency on + `mongo`, the `mongo-dev-server` package is not added, which means no local + development Mongo server is started. + [Feature Request #31](https://github.com/meteor/meteor-feature-requests/issues/31) + [PR #8853](https://github.com/meteor/meteor/pull/8853) + * `Accounts.config` no longer mistakenly allows tokens to expire when the `loginExpirationInDays` option is set to `null`. [Issue #5121](https://github.com/meteor/meteor/issues/5121) diff --git a/packages/mongo-dev-server/README.md b/packages/mongo-dev-server/README.md new file mode 100644 index 0000000000..44609c29b5 --- /dev/null +++ b/packages/mongo-dev-server/README.md @@ -0,0 +1,18 @@ +# mongo-dev-server + +[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/mongo-dev-server) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/mongo-dev-server) +*** + +When the `mongo-dev-server` package is included in a Meteor application, a +local development MongoDB server is started alongside the application. This +package is mostly used internally, as it is included by default with any +application that has a dependency on `mongo` (which is most Meteor +applications). In some cases however, people might be interested in +using the Meteor Tool without having to start a local development Mongo +instance (e.g. when using Meteor as a build system). If an application has no +dependency on `mongo`, the `mongo-dev-server` package will be removed +(since it is a direct dependency of the `mongo` package), and no local +development Mongo server will be started. + +Note this is a `debugOnly` package, meaning it will not be included in any +production bundles. diff --git a/packages/mongo-dev-server/package.js b/packages/mongo-dev-server/package.js new file mode 100644 index 0000000000..9e858ef438 --- /dev/null +++ b/packages/mongo-dev-server/package.js @@ -0,0 +1,12 @@ +Package.describe({ + debugOnly: true, + documentation: 'README.md', + name: 'mongo-dev-server', + summary: 'Start MongoDB alongside Meteor, in development mode.', + version: '1.0.0', +}); + +Package.onUse(function (api) { + api.use('modules'); + api.mainModule('server.js', 'server'); +}); diff --git a/packages/mongo-dev-server/server.js b/packages/mongo-dev-server/server.js new file mode 100644 index 0000000000..74643cab71 --- /dev/null +++ b/packages/mongo-dev-server/server.js @@ -0,0 +1,3 @@ +if (process.env.MONGO_URL === 'no-mongo-server') { + Meteor._debug('Note: Restart Meteor to start the MongoDB server.'); +} diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 07b47ecff2..ab0a9c17e6 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.1.22' + version: '1.1.23' }); Npm.depends({ @@ -34,7 +34,8 @@ Package.onUse(function (api) { 'diff-sequence', 'mongo-id', 'check', - 'ecmascript' + 'ecmascript', + 'mongo-dev-server', ]); // Binary Heap data structure is used to optimize oplog observe driver diff --git a/tools/runners/run-all.js b/tools/runners/run-all.js index da225fe8c6..2da15c02a6 100644 --- a/tools/runners/run-all.js +++ b/tools/runners/run-all.js @@ -71,10 +71,20 @@ class Runner { onFailure }); + buildmessage.capture(function () { + self.projectContext.resolveConstraints(); + }); + + const packageMap = self.projectContext.packageMap; + const hasMongoDevServerPackage = + packageMap && packageMap.getInfo('mongo-dev-server') != null; self.mongoRunner = null; if (mongoUrl) { oplogUrl = disableOplog ? null : oplogUrl; - } else { + } else if (hasMongoDevServerPackage + || process.env.METEOR_TEST_FAKE_MONGOD_CONTROL_PORT) { + // The mongo-dev-server package is required to start Mongo, but + // tests using fake-mongod are exempted. self.mongoRunner = new MongoRunner({ projectLocalDir: self.projectContext.projectLocalDir, port: mongoPort, @@ -86,6 +96,13 @@ class Runner { mongoUrl = self.mongoRunner.mongoUrl(); oplogUrl = disableOplog ? null : self.mongoRunner.oplogUrl(); + } else { + // Don't start a mongodb server. + // Set monogUrl to a specific value to prevent MongoDB connections + // and to allow a check for printing a message if `mongo-dev-server` + // is added while the app is running. + // The check and message is printed by the `mongo-dev-server` package. + mongoUrl = 'no-mongo-server'; } self.updater = new Updater(); diff --git a/tools/tests/compiler-plugins.js b/tools/tests/compiler-plugins.js index a06ce4b4b7..ad6735ce2f 100644 --- a/tools/tests/compiler-plugins.js +++ b/tools/tests/compiler-plugins.js @@ -314,11 +314,12 @@ selftest.define("compiler plugins - inactive source", () => { s.createApp('myapp', 'uses-published-package-with-inactive-source'); s.cd('myapp'); - let run = startRun(s); + const run = s.run(); + run.match('myapp'); + run.matchBeforeExit('Started proxy'); run.match('Errors prevented startup'); run.match('no plugin found for foo.sourcish in glasser:use-sourcish'); run.match('none is now'); - run.stop(); }); diff --git a/tools/tests/static-html.js b/tools/tests/static-html.js index bdb5cf9bde..fc17f67373 100644 --- a/tools/tests/static-html.js +++ b/tools/tests/static-html.js @@ -54,7 +54,7 @@ selftest.define("static-html - throws error", () => { s.cd('myapp'); const run = startRun(s); - run.match("Attributes on not supported"); + run.matchBeforeExit("Attributes on not supported"); run.stop(); }); From 7232b39fa402023b74c42248a7f4194a55844671 Mon Sep 17 00:00:00 2001 From: seke Date: Thu, 27 Jul 2017 10:25:42 +0200 Subject: [PATCH 07/53] Cut of flags to avoid abuse of CPU-intensive regexes --- packages/ejson/ejson.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index d8d0cb9471..fd7a9d4929 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -105,8 +105,11 @@ var builtinConverters = [ return { $regexp: regexp.source, $flags: regexp.flags }; }, fromJSONValue: function (obj) { - //replaces duplicate / invalid flags - return new RegExp(obj.$regexp, obj.$flags.replace(/[^gimuy]/g,'').replace(/(.)(?=.*\1)/g, '')); + // replaces duplicate / invalid flags + // cut of flags to 50 chars to avoid abusing regex for DOS + return new RegExp(obj.$regexp, obj.$flags.substr(0, 50) + .replace(/[^gimuy]/g,'') + .replace(/(.)(?=.*\1)/g, '')); } }, { // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' From ba675fe9e5fa25d1a67252c3882947aba80f5c42 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 13:18:17 +0300 Subject: [PATCH 08/53] Add `/.vscode/` to .gitignore. In the same spirit as all the other editor exceptions, like `*.sublime*` and .idea, this will allow developers to keep their own .vscode in their `meteor` directory without having to work around it constantly. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index dbdaf63a32..657108c949 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ *.iml *.sublime-project *.sublime-workspace +/.vscode/ TAGS *.log *.out From a7c229d5e5415d407ec3dd42d26f56b43f297f59 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 16:14:30 +0300 Subject: [PATCH 09/53] For `meteor deploy` there is no longer a `protection` of `password`. Only `account` is still a valid `protection` which is recognized by the Galaxy server. --- tools/meteor-services/deploy.js | 47 ++------------------------------- 1 file changed, 2 insertions(+), 45 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 53692cd668..96411c5b2e 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -205,36 +205,6 @@ var authedRpc = function (options) { return preflight ? { } : deployRpc(rpcOptions); } - if (info.protection === "password") { - if (preflight) { - return { protection: info.protection }; - } - // Password protected. Read a password, hash it, and include the - // hashed password as a query parameter when doing the RPC. - var password; - password = Console.readLine({ - echo: false, - prompt: "Password: ", - stream: process.stderr - }); - - // Hash the password so we never send plaintext over the - // wire. Doesn't actually make us more secure, but it means we - // won't leak a user's password, which they might use on other - // sites too. - var crypto = require('crypto'); - var hash = crypto.createHash('sha1'); - hash.update('S3krit Salt!'); - hash.update(password); - password = hash.digest('hex'); - - rpcOptions = _.clone(rpcOptions); - rpcOptions.qs = _.clone(rpcOptions.qs || {}); - rpcOptions.qs.password = password; - - return deployRpc(rpcOptions); - } - if (info.protection === "account") { if (! _.has(info, 'authorized')) { // Absence of this implies that we are not an authorized user on @@ -399,12 +369,7 @@ var bundleAndDeploy = function (options) { return 1; } - if (preflight.protection === "password") { - printLegacyPasswordMessage(site); - Console.error("If it's not your site, please try a different name!"); - return 1; - - } else if (preflight.protection === "account" && + if (preflight.protection === "account" && ! preflight.authorized) { printUnauthorizedMessage(); return 1; @@ -547,10 +512,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { return null; } - if (preflight.protection === "password") { - printLegacyPasswordMessage(site); - return null; - } else if (preflight.protection === "account" && + if (preflight.protection === "account" && ! preflight.authorized) { if (! auth.isLoggedIn()) { // Maybe the user is authorized for this app but not logged in @@ -659,11 +621,6 @@ var listAuthorized = function (site) { return 0; } - if (info.protection === "password") { - Console.info(""); - return 0; - } - if (info.protection === "account") { if (! _.has(info, 'authorized')) { Console.error("Couldn't get authorized users list: " + From 613ce27f69627a775a6a5071325426a14790491e Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 16:15:23 +0300 Subject: [PATCH 10/53] Remove `meteor claim` which is no longer a supported command. This was residue from Meteor Free Hosting, which is no longer available, and `meteor claim` has not worked in quite some time. --- tools/cli/commands.js | 30 +--------- tools/cli/help.txt | 10 ---- tools/meteor-services/deploy.js | 98 --------------------------------- 3 files changed, 1 insertion(+), 137 deletions(-) diff --git a/tools/cli/commands.js b/tools/cli/commands.js index bf3f8b90a6..09b4387dcb 100644 --- a/tools/cli/commands.js +++ b/tools/cli/commands.js @@ -1310,8 +1310,7 @@ main.registerCommand({ "Setting passwords on apps is no longer supported. Now there are " + "user accounts and your apps are associated with your account so " + "that only you (and people you designate) can access them. See the " + - Console.command("'meteor claim'") + " and " + - Console.command("'meteor authorized'") + " commands."); + Console.command("'meteor authorized'") + " command."); return 1; } @@ -1438,33 +1437,6 @@ main.registerCommand({ } }); -/////////////////////////////////////////////////////////////////////////////// -// claim -/////////////////////////////////////////////////////////////////////////////// - -main.registerCommand({ - name: 'claim', - minArgs: 1, - maxArgs: 1, - catalogRefresh: new catalog.Refresh.Never() -}, function (options) { - auth.pollForRegistrationCompletion(); - var site = qualifySitename(options.args[0]); - - if (! auth.isLoggedIn()) { - Console.error( - "You must be logged in to claim sites. Use " + - Console.command("'meteor login'") + " to log in. If you don't have a " + - "Meteor developer account yet, create one by clicking " + - Console.command("'Sign in'") + " and then " + - Console.command("'Create account'") + " at www.meteor.com."); - Console.error(); - return 1; - } - - return deploy.claim(site); -}); - /////////////////////////////////////////////////////////////////////////////// // test and test-packages /////////////////////////////////////////////////////////////////////////////// diff --git a/tools/cli/help.txt b/tools/cli/help.txt index 2700a1d19c..0478f17307 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -506,16 +506,6 @@ Options: --list list authorized users and organizations (the default) ->>> claim -Claim a site deployed with an old Meteor version. -Usage: meteor claim - -If you deployed a site with an old version of Meteor that did not have -support for developer accounts, you can use this command to claim that -site into your account. If you had set a password on the site you will -be prompted for it one last time. - - >>> login Log in to your Meteor developer account. Usage: meteor login [--email] diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 96411c5b2e..41eca0d620 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -240,21 +240,6 @@ var authedRpc = function (options) { }; }; -// When the user is trying to do something with a legacy -// password-protected app, instruct them to claim it with 'meteor -// claim'. -var printLegacyPasswordMessage = function (site) { - Console.error( - "\nThis site was deployed with an old version of Meteor that used " + - "site passwords instead of user accounts. Now we have a much better " + - "system, Meteor developer accounts."); - Console.error(); - Console.error("If this is your site, please claim it into your account with"); - Console.error( - Console.command("meteor claim " + site), - Console.options({ indent: 2 })); -}; - // When the user is trying to do something with an app that they are not // authorized for, instruct them to get added via 'meteor authorized // --add' or switch accounts. @@ -672,88 +657,6 @@ var changeAuthorized = function (site, action, username) { return 0; }; -var claim = function (site) { - site = canonicalizeSite(site); - if (! site) { - // canonicalizeSite will have already printed an error - return 1; - } - - // Check to see if it's even a claimable site, so that we can print - // a more appropriate message than we'd get if we called authedRpc - // straight away (at a cost of an extra REST call) - var infoResult = deployRpc({ - operation: 'info', - site: site, - printDeployURL: true - }); - if (infoResult.statusCode === 404) { - Console.error( - "There isn't a site deployed at that address. Use " + - Console.command("'meteor deploy'") + " " + - "if you'd like to deploy your app here."); - return 1; - } - - if (infoResult.payload && infoResult.payload.protection === "account") { - if (infoResult.payload.authorized) { - Console.error("That site already belongs to you.\n"); - } else { - Console.error("Sorry, that site belongs to someone else.\n"); - } - return 1; - } - - if (infoResult.payload && - infoResult.payload.protection === "password") { - Console.info( - "To claim this site and transfer it to your account, enter the", - "site password one last time."); - Console.info(); - } - - var result = authedRpc({ - method: 'POST', - operation: 'claim', - site: site, - promptIfAuthFails: true - }); - - if (result.errorMessage) { - auth.pollForRegistrationCompletion(); - if (! auth.loggedInUsername() && - auth.registrationUrl()) { - Console.error( - "You need to set a password on your Meteor developer account before", - "you can claim sites. You can do that here in under a minute:"); - Console.error(Console.url(auth.registrationUrl())); - Console.error(); - } else { - Console.error("Couldn't claim site: " + result.errorMessage); - } - return 1; - } - - Console.info(site + ": " + "successfully transferred to your account."); - Console.info(); - Console.info("Show authorized users with:"); - Console.info( - Console.command("meteor authorized " + site), - Console.options({ indent: 2 })); - Console.info(); - Console.info("Add authorized users with:"); - Console.info( - Console.command("meteor authorized " + site + " --add "), - Console.options({ indent: 2 })); - Console.info(); - Console.info("Remove authorized users with:"); - Console.info( - Console.command("meteor authorized " + site + " --remove "), - Console.options({ indent: 2 })); - Console.info(); - return 0; -}; - var listSites = function () { var result = deployRpc({ method: "GET", @@ -863,5 +766,4 @@ exports.temporaryMongoUrl = temporaryMongoUrl; exports.logs = logs; exports.listAuthorized = listAuthorized; exports.changeAuthorized = changeAuthorized; -exports.claim = claim; exports.listSites = listSites; From aedd111cbd9151aaf30b993cb8456489c9c498d3 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Thu, 27 Jul 2017 09:48:53 -0400 Subject: [PATCH 11/53] Increase "modules - test app" self-test wait time to fix Circle (#8948) * Increase "modules - test app" self-test wait time to fix Circle failures. * Increase "modules - test app" self-test wait time further. --- tools/tests/modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tests/modules.js b/tools/tests/modules.js index 5fa92593d2..c9fac18c9b 100644 --- a/tools/tests/modules.js +++ b/tools/tests/modules.js @@ -24,7 +24,7 @@ selftest.define("modules - test app", function () { "--driver-package", "dispatch:mocha-phantomjs" ); - run.waitSecs(60); + run.waitSecs(180); run.match("App running at"); run.match("SERVER FAILURES: 0"); run.match("CLIENT FAILURES: 0"); From d35f9b52ff0701c03e102ebda39c302e3b7d8eb4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 18:51:28 +0300 Subject: [PATCH 12/53] Reworking `function`s and `export`s in tools/meteor-services/deploy.js. --- tools/meteor-services/deploy.js | 32 ++++++++++++-------------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 41eca0d620..be658c5b24 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -58,7 +58,7 @@ const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // derived from either a transport-level exception, the response // body, or a generic 'try again later' message, as appropriate -var deployRpc = function (options) { +function deployRpc(options) { options = _.clone(options); options.headers = _.clone(options.headers || {}); if (options.headers.cookie) { @@ -152,7 +152,7 @@ var deployRpc = function (options) { // accounts server but our authentication actually fails, then prompt // the user to log in with a username and password and then resend the // RPC. -var authedRpc = function (options) { +function authedRpc(options) { var rpcOptions = _.clone(options); var preflight = rpcOptions.preflight; delete rpcOptions.preflight; @@ -243,7 +243,7 @@ var authedRpc = function (options) { // When the user is trying to do something with an app that they are not // authorized for, instruct them to get added via 'meteor authorized // --add' or switch accounts. -var printUnauthorizedMessage = function () { +function printUnauthorizedMessage() { var username = auth.loggedInUsername(); Console.error("Sorry, that site belongs to a different user."); if (username) { @@ -261,7 +261,7 @@ var printUnauthorizedMessage = function () { // syntactically good, canonicalize it (this essentially means // stripping 'http://' or a trailing '/' if present) and return it. If // not, print an error message to stderr and return null. -var canonicalizeSite = function (site) { +function canonicalizeSite(site) { // There are actually two different bugs here. One is that the meteor deploy // server does not support apps whose total site length is greater than 63 // (because of how it generates Mongo database names); that can be fixed on @@ -314,7 +314,7 @@ var canonicalizeSite = function (site) { // stats server. // - buildOptions: the 'buildOptions' argument to the bundler // - rawOptions: any unknown options that were passed to the command line tool -var bundleAndDeploy = function (options) { +export function bundleAndDeploy(options) { if (options.recordPackageUsage === undefined) { options.recordPackageUsage = true; } @@ -452,7 +452,7 @@ var bundleAndDeploy = function (options) { return 0; }; -var deleteApp = function (site) { +export function deleteApp(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -483,7 +483,7 @@ var deleteApp = function (site) { // messages. Returns the result of the RPC if successful, or null // otherwise (including if auth failed or if the user is not authorized // for this site). -var checkAuthThenSendRpc = function (site, operation, what) { +function checkAuthThenSendRpc(site, operation, what) { var preflight = authedRpc({ operation: operation, site: site, @@ -550,7 +550,7 @@ var checkAuthThenSendRpc = function (site, operation, what) { // On failure, prints a message to stderr and returns null. Otherwise, // returns a temporary authenticated Mongo URL allowing access to this // site's database. -var temporaryMongoUrl = function (site) { +export function temporaryMongoUrl(site) { site = canonicalizeSite(site); if (! site) { // canonicalizeSite printed an error @@ -566,7 +566,7 @@ var temporaryMongoUrl = function (site) { } }; -var logs = function (site) { +export function logs(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -583,7 +583,7 @@ var logs = function (site) { } }; -var listAuthorized = function (site) { +export function listAuthorized(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -627,7 +627,7 @@ var listAuthorized = function (site) { }; // action is "add", "transfer" or "remove" -var changeAuthorized = function (site, action, username) { +export function changeAuthorized(site, action, username) { site = canonicalizeSite(site); if (! site) { // canonicalizeSite will have already printed an error @@ -657,7 +657,7 @@ var changeAuthorized = function (site, action, username) { return 0; }; -var listSites = function () { +export function listSites() { var result = deployRpc({ method: "GET", operation: "authorized-apps", @@ -759,11 +759,3 @@ async function discoverGalaxy(site, scheme) { } return body.deployURL; } - -exports.bundleAndDeploy = bundleAndDeploy; -exports.deleteApp = deleteApp; -exports.temporaryMongoUrl = temporaryMongoUrl; -exports.logs = logs; -exports.listAuthorized = listAuthorized; -exports.changeAuthorized = changeAuthorized; -exports.listSites = listSites; From 639436b9ef1e66d7f529041fa541d59b7b22bb81 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 18:54:16 +0300 Subject: [PATCH 13/53] Replace underscore `_.has` with `Object.property.hasOwnProperty`. --- tools/meteor-services/deploy.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index be658c5b24..0f4d172055 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -13,6 +13,8 @@ var _ = require('underscore'); var stats = require('./stats.js'); var Console = require('../console/console.js').Console; +const hasOwn = Object.prototype.hasOwnProperty; + const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // Make a synchronous RPC to the "classic" MDG deploy API. The deploy @@ -119,11 +121,11 @@ function deployRpc(options) { var hasAllExpectedKeys = _.all(_.map( options.expectPayload || [], function (key) { - return ret.payload && _.has(ret.payload, key); + return ret.payload && hasOwn.call(ret.payload, key); })); - if ((options.expectPayload && ! _.has(ret, 'payload')) || - (options.expectMessage && ! _.has(ret, 'message')) || + if ((options.expectPayload && ! hasOwn.call(ret, 'payload')) || + (options.expectMessage && ! hasOwn.call(ret, 'message')) || ! hasAllExpectedKeys) { delete ret.payload; delete ret.message; @@ -198,7 +200,7 @@ function authedRpc(options) { } var info = infoResult.payload; - if (! _.has(info, 'protection')) { + if (! hasOwn.call(info, 'protection')) { // Not protected. // // XXX should prompt the user to claim the app (only if deploying?) @@ -206,7 +208,7 @@ function authedRpc(options) { } if (info.protection === "account") { - if (! _.has(info, 'authorized')) { + if (! hasOwn.call(info, 'authorized')) { // Absence of this implies that we are not an authorized user on // this app if (preflight) { @@ -601,13 +603,13 @@ export function listAuthorized(site) { } var info = result.payload; - if (! _.has(info, 'protection')) { + if (! hasOwn.call(info, 'protection')) { Console.info(""); return 0; } if (info.protection === "account") { - if (! _.has(info, 'authorized')) { + if (! hasOwn.call(info, 'authorized')) { Console.error("Couldn't get authorized users list: " + "You are not authorized"); return 1; @@ -754,7 +756,7 @@ async function discoverGalaxy(site, scheme) { throw new Error( "unexpected galaxyDiscoveryVersion: " + body.galaxyDiscoveryVersion); } - if (!_.has(body, "deployURL")) { + if (! hasOwn.call(body, "deployURL")) { throw new Error("no deployURL"); } return body.deployURL; From f896950bfc9c257e801afa9f5d7841bab81d47db Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 18:55:56 +0300 Subject: [PATCH 14/53] Replace underscore `_.extend` with `Object.assign`. --- tools/meteor-services/deploy.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 0f4d172055..d4a083336f 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -66,7 +66,7 @@ function deployRpc(options) { if (options.headers.cookie) { throw new Error("sorry, can't combine cookie headers yet"); } - options.qs = _.extend({}, options.qs, {capabilities: CAPABILITIES}); + options.qs = Object.assign({}, options.qs, {capabilities: CAPABILITIES}); const deployURLBase = getDeployURL(options.site).await(); @@ -410,7 +410,7 @@ export function bundleAndDeploy(options) { method: 'POST', operation: 'deploy', site: site, - qs: _.extend({}, options.rawOptions, settings !== null ? {settings: settings} : {}), + qs: Object.assign({}, options.rawOptions, settings !== null ? {settings: settings} : {}), bodyStream: files.createTarGzStream(files.pathJoin(buildDir, 'bundle')), expectPayload: ['url'], preflightPassword: preflight.preflightPassword, From ae9eaf69ac60c7d3583bb65b2701f262d7262d5b Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 18:57:58 +0300 Subject: [PATCH 15/53] Change `require` statements to `import` statements. --- tools/meteor-services/deploy.js | 58 +++++++++++++++++++-------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index d4a083336f..991d55bd84 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -4,14 +4,24 @@ // prompt for password // send RPC with or without password as required -var files = require('../fs/files.js'); -var httpHelpers = require('../utils/http-helpers.js'); -var buildmessage = require('../utils/buildmessage.js'); -var config = require('./config.js'); -var auth = require('./auth.js'); -var _ = require('underscore'); -var stats = require('./stats.js'); -var Console = require('../console/console.js').Console; +import { + pathJoin, + createTarGzStream, + getSettings, + mkdtemp, +} from '../fs/files.js'; +import { request } from '../utils/http-helpers.js'; +import buildmessage from '../utils/buildmessage.js'; +import { + pollForRegistrationCompletion, + doInteractivePasswordLogin, + loggedInUsername, + isLoggedIn, + maybePrintRegistrationLink, +} from './auth.js'; +import _ from 'underscore'; +import { recordPackages } from './stats.js'; +import { Console } from '../console/console.js'; const hasOwn = Object.prototype.hasOwnProperty; @@ -76,7 +86,7 @@ function deployRpc(options) { // XXX: Reintroduce progress for upload try { - var result = httpHelpers.request(_.extend(options, { + var result = request(Object.assign(options, { url: deployURLBase + '/' + options.operation + (options.site ? ('/' + options.site) : ''), method: options.method || 'GET', @@ -180,7 +190,7 @@ function authedRpc(options) { username: username, suppressErrorMessage: true }; - if (auth.doInteractivePasswordLogin(loginOptions)) { + if (doInteractivePasswordLogin(loginOptions)) { return authedRpc(options); } else { return { @@ -216,7 +226,7 @@ function authedRpc(options) { } else { return { statusCode: null, - errorMessage: auth.isLoggedIn() ? + errorMessage: isLoggedIn() ? // XXX better error message (probably need to break out of // the 'errorMessage printed with brief prefix' pattern) "Not an authorized user on this site" : @@ -246,7 +256,7 @@ function authedRpc(options) { // authorized for, instruct them to get added via 'meteor authorized // --add' or switch accounts. function printUnauthorizedMessage() { - var username = auth.loggedInUsername(); + var username = loggedInUsername(); Console.error("Sorry, that site belongs to a different user."); if (username) { Console.error("You are currently logged in as " + username + "."); @@ -336,10 +346,10 @@ export function bundleAndDeploy(options) { // they'll get an email prompt instead of a username prompt because // the command-line tool didn't have time to learn about their // username before the credential was expired. - auth.pollForRegistrationCompletion({ + pollForRegistrationCompletion({ noLogout: true }); - var promptIfAuthFails = (auth.loggedInUsername() !== null); + var promptIfAuthFails = (loggedInUsername() !== null); // Check auth up front, rather than after the (potentially lengthy) // bundling process. @@ -362,8 +372,8 @@ export function bundleAndDeploy(options) { return 1; } - var buildDir = files.mkdtemp('build_tar'); - var bundlePath = files.pathJoin(buildDir, 'bundle'); + var buildDir = mkdtemp('build_tar'); + var bundlePath = pathJoin(buildDir, 'bundle'); Console.info('Deploying your app...'); @@ -373,7 +383,7 @@ export function bundleAndDeploy(options) { rootPath: process.cwd() }, function () { if (options.settingsFile) { - settings = files.getSettings(options.settingsFile); + settings = getSettings(options.settingsFile); } }); @@ -398,7 +408,7 @@ export function bundleAndDeploy(options) { } if (options.recordPackageUsage) { - stats.recordPackages({ + recordPackages({ what: "sdk.deploy", projectContext: options.projectContext, site: site @@ -411,7 +421,7 @@ export function bundleAndDeploy(options) { operation: 'deploy', site: site, qs: Object.assign({}, options.rawOptions, settings !== null ? {settings: settings} : {}), - bodyStream: files.createTarGzStream(files.pathJoin(buildDir, 'bundle')), + bodyStream: createTarGzStream(pathJoin(buildDir, 'bundle')), expectPayload: ['url'], preflightPassword: preflight.preflightPassword, // Disable the HTTP timeout for this POST request. @@ -501,10 +511,10 @@ function checkAuthThenSendRpc(site, operation, what) { if (preflight.protection === "account" && ! preflight.authorized) { - if (! auth.isLoggedIn()) { + if (! isLoggedIn()) { // Maybe the user is authorized for this app but not logged in // yet, so give them a login prompt. - var loginResult = auth.doUsernamePasswordLogin({ retry: true }); + var loginResult = doUsernamePasswordLogin({ retry: true }); if (loginResult) { // Once we've logged in, retry the whole operation. We need to // do the preflight request again instead of immediately moving @@ -580,7 +590,7 @@ export function logs(site) { return 1; } else { Console.info(result.message); - auth.maybePrintRegistrationLink({ leadingNewline: true }); + maybePrintRegistrationLink({ leadingNewline: true }); return 0; } }; @@ -615,7 +625,7 @@ export function listAuthorized(site) { return 1; } - Console.info((auth.loggedInUsername() || "")); + Console.info((loggedInUsername() || "")); _.each(info.authorized, function (username) { if (username) { // Current username rules don't let you register anything that we might @@ -738,7 +748,7 @@ async function discoverGalaxy(site, scheme) { scheme + "://" + site + "/.well-known/meteor/deploy-url"; // If httpHelpers.request throws, the returned Promise will reject, which is // fine. - const { response, body } = httpHelpers.request({ + const { response, body } = request({ url: discoveryURL, json: true, strictSSL: true, From 31735e087d9471f1200e7b1dc0bb6465d901c5fc Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 19:22:56 +0300 Subject: [PATCH 16/53] Replace underscore `_.each` with `Array.prototype.forEach`. --- tools/meteor-services/deploy.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 991d55bd84..e3f2724eb3 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -129,10 +129,10 @@ function deployRpc(options) { ret.message = body; } - var hasAllExpectedKeys = _.all(_.map( - options.expectPayload || [], function (key) { - return ret.payload && hasOwn.call(ret.payload, key); - })); + const hasAllExpectedKeys = + (options.expectPayload || []) + .map(key => ret.payload && hasOwn.call(ret.payload, key)) + .every(x => x); if ((options.expectPayload && ! hasOwn.call(ret, 'payload')) || (options.expectMessage && ! hasOwn.call(ret, 'message')) || @@ -626,7 +626,7 @@ export function listAuthorized(site) { } Console.info((loggedInUsername() || "")); - _.each(info.authorized, function (username) { + info.authorized.forEach(username => { if (username) { // Current username rules don't let you register anything that we might // want to split over multiple lines (ex: containing a space), but we @@ -687,10 +687,9 @@ export function listSites() { ! result.payload.sites.length) { Console.info("You don't have any sites yet."); } else { - result.payload.sites.sort(); - _.each(result.payload.sites, function (site) { - Console.info(site); - }); + result.payload.sites + .sort() + .forEach(site => Console.info(site)); } return 0; }; From 300511450bf0f29686a0b285ea0ddca158d3d5fc Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 19:32:26 +0300 Subject: [PATCH 17/53] Change `import` of `underscore` to only bring in `clone`. --- tools/meteor-services/deploy.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index e3f2724eb3..37f65e5e0c 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -19,7 +19,7 @@ import { isLoggedIn, maybePrintRegistrationLink, } from './auth.js'; -import _ from 'underscore'; +import { clone } from 'underscore'; import { recordPackages } from './stats.js'; import { Console } from '../console/console.js'; @@ -71,8 +71,8 @@ const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // body, or a generic 'try again later' message, as appropriate function deployRpc(options) { - options = _.clone(options); - options.headers = _.clone(options.headers || {}); + options = clone(options); + options.headers = clone(options.headers || {}); if (options.headers.cookie) { throw new Error("sorry, can't combine cookie headers yet"); } @@ -165,7 +165,7 @@ function deployRpc(options) { // the user to log in with a username and password and then resend the // RPC. function authedRpc(options) { - var rpcOptions = _.clone(options); + var rpcOptions = clone(options); var preflight = rpcOptions.preflight; delete rpcOptions.preflight; From 96db56b0bae5168fa852eead7cbf0131a12c0ec8 Mon Sep 17 00:00:00 2001 From: Stephen Darnell Date: Fri, 28 Jul 2017 22:36:07 +0100 Subject: [PATCH 18/53] Add 'meteor list --tree' to show a tree of package dependencies. (#8936) There's also a --weak command line option which when specified also shows weak dependencies. --- tools/cli/commands-packages.js | 82 ++++++++++++++++++++++++++++++++++ tools/cli/help.txt | 7 ++- 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index f0fe6f5d50..265d41e163 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1138,6 +1138,8 @@ main.registerCommand({ name: 'list', requiresApp: true, options: { + 'tree': { type: Boolean }, + 'weak': { type: Boolean }, 'allow-incompatible-update': { type: Boolean } }, catalogRefresh: new catalog.Refresh.OnceAtStart({ ignoreErrors: true }) @@ -1152,6 +1154,86 @@ main.registerCommand({ // No need to display the PackageMapDelta here, since we're about to list all // of the packages anyway! + if (options['tree']) { + const showWeak = !!options['weak']; + // Load package details of all used packages (inc. dependencies) + const packageDetails = new Map; + projectContext.packageMap.eachPackage(function (name, info) { + packageDetails.set(name, projectContext.projectCatalog.getVersion(name, info.version)); + }); + + // Build a set of top level package names + const topLevelSet = new Set; + projectContext.projectConstraintsFile.eachConstraint(function (constraint) { + topLevelSet.add(constraint.package); + }); + + // Package that should not be expanded (top level or expanded already) + const dontExpand = new Set(topLevelSet.values()); + + // Recursive function that outputs each package + const printPackage = function (package, isWeak, indent1, indent2) { + const packageName = package.packageName; + const depsObj = package.dependencies || {}; + let deps = Object.keys(depsObj).sort(); + // Ignore references to a meteor version or isobuild marker packages + deps = deps.filter(dep => { + return dep !== 'meteor' && !compiler.isIsobuildFeaturePackage(dep); + }); + + if (!showWeak) { + // Filter out any weakly referenced dependencies + deps = deps.filter(dep => { + let references = depsObj[dep].references || []; + let weakRef = references.length > 0 && references.every(r => r.weak); + return !weakRef; + }); + } + + const expandedAlready = (deps.length > 0 && dontExpand.has(packageName)); + const shouldExpand = (deps.length > 0 && !expandedAlready && !isWeak); + if (indent1 !== '') { + indent1 += (shouldExpand ? '┬' : '─') + ' '; + } + + let suffix = (isWeak ? '[weak]' : ''); + if (expandedAlready) { + suffix += topLevelSet.has(packageName) ? ' (top level)' : ' (expanded above)'; + } + + Console.info(indent1 + packageName + '@' + package.version + suffix); + if (shouldExpand) { + dontExpand.add(packageName); + deps.forEach((dep, index) => { + const references = depsObj[dep].references || []; + const weakRef = references.length > 0 && references.every(r => r.weak); + const last = ((index + 1) === deps.length); + const child = packageDetails.get(dep); + const newIndent1 = indent2 + (last ? '└─' : '├─'); + const newIndent2 = indent2 + (last ? ' ' : '│ '); + if (child) { + printPackage(child, weakRef, newIndent1, newIndent2); + } else if (weakRef) { + Console.info(newIndent1 + '─ ' + dep + '[weak] package skipped'); + } else { + Console.info(newIndent1 + '─ ' + dep + ' missing?'); + } + }); + } + }; + + const topLevelNames = Array.from(topLevelSet.values()).sort(); + topLevelNames.forEach((dep, index) => { + const package = packageDetails.get(dep); + if (package) { + // Force top level packages to be expanded + dontExpand.delete(package.packageName); + printPackage(package, false, '', ''); + } + }); + + return 0; + } var items = []; var newVersionsAvailable = false; diff --git a/tools/cli/help.txt b/tools/cli/help.txt index 2700a1d19c..cb51a027e5 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -248,9 +248,14 @@ Options: >>> list List the packages explicitly used by your project. Usage: meteor list + meteor list --tree [--weak] Lists the packages that you have explicitly added to your project. -This will not list transitive dependencies. +Transitive dependencies are not listed unless you use the --tree option, +which outputs a tree showing how packages are referenced. + +Options: + --weak Show weakly referenced dependencies in the tree. >>> add-platform Add a platform to this project. From d4a1322d3a0bc16f36b0fea5136e3f785d5f133e Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Fri, 28 Jul 2017 18:55:45 -0400 Subject: [PATCH 19/53] Bump package versions for 1.5.2-beta.7 release. --- packages/accounts-base/package.js | 2 +- packages/babel-compiler/package.js | 2 +- packages/boilerplate-generator-tests/package.js | 2 +- packages/boilerplate-generator/package.js | 2 +- packages/ejson/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/modules/package.js | 2 +- packages/mongo-dev-server/package.js | 2 +- packages/mongo/package.js | 2 +- packages/promise/package.js | 2 +- packages/webapp/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index c2bffb408d..8d73182a09 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "A user account system", - version: "1.3.2-beta152.6" + version: "1.3.2-beta152.7" }); Package.onUse(function (api) { diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 00fc163358..325a917e7a 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -6,7 +6,7 @@ Package.describe({ // isn't possible because you can't publish a non-recommended // release with package versions that don't have a pre-release // identifier at the end (eg, -dev) - version: '6.20.0-beta152.6' + version: '6.20.0-beta152.7' }); Npm.depends({ diff --git a/packages/boilerplate-generator-tests/package.js b/packages/boilerplate-generator-tests/package.js index 9228002677..d2c3e859cc 100644 --- a/packages/boilerplate-generator-tests/package.js +++ b/packages/boilerplate-generator-tests/package.js @@ -2,7 +2,7 @@ Package.describe({ // These tests are in a separate package so that we can Npm.depend on // parse5, a html parsing library. summary: "Tests for the boilerplate-generator package", - version: '1.0.0-beta152.6', + version: '1.0.0-beta152.7', documentation: null }); diff --git a/packages/boilerplate-generator/package.js b/packages/boilerplate-generator/package.js index ea432d640c..29554e596e 100644 --- a/packages/boilerplate-generator/package.js +++ b/packages/boilerplate-generator/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Generates the boilerplate html from program's manifest", - version: '1.2.0-beta152.6' + version: '1.2.0-beta152.7' }); Package.onUse(api => { diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 27c712f5a3..b1df5c34ba 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Extended and Extensible JSON library", - version: '1.0.13' + version: '1.0.14-beta152.7' }); Package.onUse(function (api) { diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 4a3729b4e2..32942c4778 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "The Meteor command-line tool", - version: "1.5.2-beta.6" + version: "1.5.2-beta.7" }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index c121ae4c34..fab2ed036a 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.3.0-beta152.6' + version: '1.3.0-beta152.7' }); Package.onUse(function (api) { diff --git a/packages/modules/package.js b/packages/modules/package.js index 75949dac24..4d45c480a9 100644 --- a/packages/modules/package.js +++ b/packages/modules/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "modules", - version: "0.10.0-beta152.6", + version: "0.10.0-beta152.7", summary: "CommonJS module system", documentation: "README.md" }); diff --git a/packages/mongo-dev-server/package.js b/packages/mongo-dev-server/package.js index 9e858ef438..bf75976d23 100644 --- a/packages/mongo-dev-server/package.js +++ b/packages/mongo-dev-server/package.js @@ -3,7 +3,7 @@ Package.describe({ documentation: 'README.md', name: 'mongo-dev-server', summary: 'Start MongoDB alongside Meteor, in development mode.', - version: '1.0.0', + version: '1.0.1-beta152.7', }); Package.onUse(function (api) { diff --git a/packages/mongo/package.js b/packages/mongo/package.js index f02a6a7f2f..abd6da9650 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.2.0-beta152.6' + version: '1.2.0-beta152.7' }); Npm.depends({ diff --git a/packages/promise/package.js b/packages/promise/package.js index 564b636170..bf29ce1d36 100644 --- a/packages/promise/package.js +++ b/packages/promise/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "promise", - version: "0.9.0-beta152.6", + version: "0.9.0-beta152.7", summary: "ECMAScript 2015 Promise polyfill with Fiber support", git: "https://github.com/meteor/promise", documentation: "README.md" diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 7f1c3af21e..3958543cb4 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Serves a Meteor app over HTTP", - version: '1.3.18-beta152.6' + version: '1.3.18-beta152.7' }); Npm.depends({connect: "2.30.2", diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 865e5ba77a..14bc097004 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "1.5.2-beta.6", + "version": "1.5.2-beta.7", "recommended": false, "official": false, "description": "Meteor" From 3cdcca6f6a3d944dfbb77da0894979cfca923869 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 31 Jul 2017 11:50:36 +0300 Subject: [PATCH 20/53] Replace underscore `_.clone` with `Object.assign`. --- tools/meteor-services/deploy.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 37f65e5e0c..e0ff7afcaa 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -19,7 +19,6 @@ import { isLoggedIn, maybePrintRegistrationLink, } from './auth.js'; -import { clone } from 'underscore'; import { recordPackages } from './stats.js'; import { Console } from '../console/console.js'; @@ -71,8 +70,8 @@ const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; // body, or a generic 'try again later' message, as appropriate function deployRpc(options) { - options = clone(options); - options.headers = clone(options.headers || {}); + options = Object.assign({}, options); + options.headers = Object.assign({}, options.headers || {}); if (options.headers.cookie) { throw new Error("sorry, can't combine cookie headers yet"); } @@ -165,7 +164,7 @@ function deployRpc(options) { // the user to log in with a username and password and then resend the // RPC. function authedRpc(options) { - var rpcOptions = clone(options); + var rpcOptions = Object.assign({}, options); var preflight = rpcOptions.preflight; delete rpcOptions.preflight; From 19e33cba496b85e866b24e45d45377eebf492385 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 31 Jul 2017 23:44:21 +0300 Subject: [PATCH 21/53] Update `longjohn` to latest version to maybe fix SIGSEGV CI errors. --- scripts/dev-bundle-tool-package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/dev-bundle-tool-package.js b/scripts/dev-bundle-tool-package.js index 6b0ab17ba4..859a5fa26d 100644 --- a/scripts/dev-bundle-tool-package.js +++ b/scripts/dev-bundle-tool-package.js @@ -56,7 +56,7 @@ var packageJson = { optimism: "0.3.3", 'lru-cache': '4.0.1', 'cordova-lib': "6.4.0", - longjohn: '0.2.11' + longjohn: '0.2.12' } }; From ebe163806e3c5fe5e1f231fe4df89303155b032f Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 1 Aug 2017 00:07:31 +0300 Subject: [PATCH 22/53] Bump $BUNDLE_VERSION to 9999.8970.0 before rebuilding dev bundle. To test meteor/meteor#8970. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index ef8749df65..ec688d925b 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.8.21 +BUNDLE_VERSION=9999.8970.0 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From dca2ced0147c0bc2902c33dbe5fede19a2dbd3a0 Mon Sep 17 00:00:00 2001 From: Stephen Darnell Date: Tue, 1 Aug 2017 16:41:32 +0100 Subject: [PATCH 23/53] Avoid new uses of 'package' as it is apparently a reserved keyword. (#8973) --- tools/cli/commands-packages.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/cli/commands-packages.js b/tools/cli/commands-packages.js index 265d41e163..661fe494ca 100644 --- a/tools/cli/commands-packages.js +++ b/tools/cli/commands-packages.js @@ -1172,9 +1172,9 @@ main.registerCommand({ const dontExpand = new Set(topLevelSet.values()); // Recursive function that outputs each package - const printPackage = function (package, isWeak, indent1, indent2) { - const packageName = package.packageName; - const depsObj = package.dependencies || {}; + const printPackage = function (packageToPrint, isWeak, indent1, indent2) { + const packageName = packageToPrint.packageName; + const depsObj = packageToPrint.dependencies || {}; let deps = Object.keys(depsObj).sort(); // Ignore references to a meteor version or isobuild marker packages deps = deps.filter(dep => { @@ -1201,7 +1201,7 @@ main.registerCommand({ suffix += topLevelSet.has(packageName) ? ' (top level)' : ' (expanded above)'; } - Console.info(indent1 + packageName + '@' + package.version + suffix); + Console.info(indent1 + packageName + '@' + packageToPrint.version + suffix); if (shouldExpand) { dontExpand.add(packageName); deps.forEach((dep, index) => { @@ -1224,11 +1224,11 @@ main.registerCommand({ const topLevelNames = Array.from(topLevelSet.values()).sort(); topLevelNames.forEach((dep, index) => { - const package = packageDetails.get(dep); - if (package) { + const topLevelPackage = packageDetails.get(dep); + if (topLevelPackage) { // Force top level packages to be expanded - dontExpand.delete(package.packageName); - printPackage(package, false, '', ''); + dontExpand.delete(topLevelPackage.packageName); + printPackage(topLevelPackage, false, '', ''); } }); From 82ae87184f74ff125a3bad4a6d30ab71461e7d11 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 1 Aug 2017 22:28:08 +0300 Subject: [PATCH 24/53] Revert "Bump $BUNDLE_VERSION to 9999.8970.0 before rebuilding dev bundle." This reverts commit ebe163806e3c5fe5e1f231fe4df89303155b032f. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index ec688d925b..ef8749df65 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=9999.8970.0 +BUNDLE_VERSION=4.8.21 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 777a3eb9f770cc09488aea791d1c14c6b7c71989 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Tue, 1 Aug 2017 22:52:22 +0300 Subject: [PATCH 25/53] Bump $BUNDLE_VERSION to 4.8.29 before rebuilding dev bundle. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index eb80a8baf7..144420a751 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.8.28 +BUNDLE_VERSION=4.8.29 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From c3dd664d14b469acb9b44f46d6f872bf61720da1 Mon Sep 17 00:00:00 2001 From: James Burgess Date: Wed, 2 Aug 2017 17:43:43 +0200 Subject: [PATCH 26/53] Adjust minimongo behavior to match server when functions are part of selectors (#8952) --- packages/minimongo/minimongo_tests.js | 8 ++++++++ packages/minimongo/selector.js | 13 +++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/packages/minimongo/minimongo_tests.js b/packages/minimongo/minimongo_tests.js index 6d1e5b9e3c..5fa47d11d3 100644 --- a/packages/minimongo/minimongo_tests.js +++ b/packages/minimongo/minimongo_tests.js @@ -161,6 +161,14 @@ Tinytest.add("minimongo - basics", function (test) { test.equal(c.find({foo: {bam: 'baz'}}).count(), 0); test.equal(c.find({foo: {bar: 'baz'}}).count(), 1); + // Regression test for #5301 + c.remove({}); + c.insert({ a: 'a', b: 'b' }); + const noop = () => null; + test.equal(c.find({ a: noop }).count(), 1); + test.equal(c.find({ a: 'a', b: noop }).count(), 1); + test.equal(c.find({ c: noop }).count(), 1); + test.equal(c.find({ a: noop, c: 'c' }).count(), 0); }); Tinytest.add("minimongo - error - no options", function (test) { diff --git a/packages/minimongo/selector.js b/packages/minimongo/selector.js index 99720962a0..1bc1ab791c 100644 --- a/packages/minimongo/selector.js +++ b/packages/minimongo/selector.js @@ -142,10 +142,15 @@ var compileDocumentSelector = function (docSelector, matcher, options) { var lookUpByIndex = makeLookupFunction(key); var valueMatcher = compileValueSelector(subSelector, matcher, options.isRoot); - docMatchers.push(function (doc) { - var branchValues = lookUpByIndex(doc); - return valueMatcher(branchValues); - }); + // Don't add a matcher if subSelector is a function -- this is to match + // the behavior of Meteor on the server (inherited from the node mongodb + // driver), which is to ignore any part of a selector which is a function. + if (typeof subSelector !== 'function') { + docMatchers.push(function (doc) { + var branchValues = lookUpByIndex(doc); + return valueMatcher(branchValues); + }); + } } }); From 4665f2aab3a9344a9fc5e17d31e57a70f3da05e4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Wed, 2 Aug 2017 18:50:01 +0300 Subject: [PATCH 27/53] Include the Node.js and npm version in the `star.json` manifest. (#8956) * Include the Node.js and npm version in the `star.json` manifest. This makes it possible to know exactly which version of Node.js and npm were used by the `meteor` command from which the bundle was built from. * History.md for #8956. --- History.md | 7 +++++++ tools/isobuild/bundler.js | 4 +++- tools/isobuild/meteor-npm.js | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 087aae7f0c..113cb7a099 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,12 @@ ## v.NEXT +* The `star.json` manifest created within the root of a `meteor build` bundle + will now contain `nodeVersion` and `npmVersion` which will specify the exact + versions of Node.js and npm (respectively) which the Meteor release was + bundled with. The `.node_version.txt` file will still be written into the + root of the bundle, but it may be deprecated in a future version of Meteor. + [PR #8956](https://github.com/meteor/meteor/pull/8956) + * A new package called `mongo-dev-server` has been created and wired into `mongo` as a dependency. As long as this package is included in a Meteor application (which it is by default since all new Meteor apps have `mongo` diff --git a/tools/isobuild/bundler.js b/tools/isobuild/bundler.js index 21cdc50ecf..cd66820910 100644 --- a/tools/isobuild/bundler.js +++ b/tools/isobuild/bundler.js @@ -2628,7 +2628,9 @@ var writeSiteArchive = Profile("bundler writeSiteArchive", function ( format: "site-archive-pre1", builtBy, programs: [], - meteorRelease: releaseName + meteorRelease: releaseName, + nodeVersion: process.versions.node, + npmVersion: meteorNpm.npmVersion, }; var nodePath = []; diff --git a/tools/isobuild/meteor-npm.js b/tools/isobuild/meteor-npm.js index 4ec6c3086c..497e85d805 100644 --- a/tools/isobuild/meteor-npm.js +++ b/tools/isobuild/meteor-npm.js @@ -14,6 +14,7 @@ var buildmessage = require('../utils/buildmessage.js'); var utils = require('../utils/utils.js'); var runLog = require('../runners/run-log.js'); var Profile = require('../tool-env/profile.js').Profile; +import { version as npmVersion } from 'npm'; import { execFileAsync } from "../utils/processes.js"; import { get as getRebuildArgs @@ -33,6 +34,9 @@ import { var meteorNpm = exports; +// Expose the version of npm in use from the dev bundle. +meteorNpm.npmVersion = npmVersion; + // if a user exits meteor while we're trying to create a .npm // directory, we will have temporary directories that we clean up var tmpDirs = []; From ce0b4992240af386f420824e2f5084a00b4838ee Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 13:36:24 -0400 Subject: [PATCH 28/53] Move old packages from packages/non-core to packages/deprecated. --- packages/{non-core => deprecated}/jquery-history/.gitignore | 0 .../jquery-history/history.adapter.jquery.js | 0 packages/{non-core => deprecated}/jquery-history/history.html4.js | 0 packages/{non-core => deprecated}/jquery-history/history.js | 0 packages/{non-core => deprecated}/jquery-history/package.js | 0 packages/{non-core => deprecated}/jquery-layout/.gitignore | 0 packages/{non-core => deprecated}/jquery-layout/jquery.layout.js | 0 packages/{non-core => deprecated}/jquery-layout/package.js | 0 packages/{non-core => deprecated}/jquery-waypoints/.gitignore | 0 packages/{non-core => deprecated}/jquery-waypoints/README.md | 0 packages/{non-core => deprecated}/jquery-waypoints/package.js | 0 .../{non-core => deprecated}/jquery-waypoints/waypoints.coffee | 0 packages/{non-core => deprecated}/spiderable/.gitignore | 0 packages/{non-core => deprecated}/spiderable/README.md | 0 packages/{non-core => deprecated}/spiderable/package.js | 0 packages/{non-core => deprecated}/spiderable/phantom_script.js | 0 packages/{non-core => deprecated}/spiderable/spiderable.html | 0 packages/{non-core => deprecated}/spiderable/spiderable.js | 0 packages/{non-core => deprecated}/spiderable/spiderable_client.js | 0 .../spiderable/spiderable_client_tests.js | 0 packages/{non-core => deprecated}/spiderable/spiderable_server.js | 0 .../spiderable/spiderable_server_tests.js | 0 22 files changed, 0 insertions(+), 0 deletions(-) rename packages/{non-core => deprecated}/jquery-history/.gitignore (100%) rename packages/{non-core => deprecated}/jquery-history/history.adapter.jquery.js (100%) rename packages/{non-core => deprecated}/jquery-history/history.html4.js (100%) rename packages/{non-core => deprecated}/jquery-history/history.js (100%) rename packages/{non-core => deprecated}/jquery-history/package.js (100%) rename packages/{non-core => deprecated}/jquery-layout/.gitignore (100%) rename packages/{non-core => deprecated}/jquery-layout/jquery.layout.js (100%) rename packages/{non-core => deprecated}/jquery-layout/package.js (100%) rename packages/{non-core => deprecated}/jquery-waypoints/.gitignore (100%) rename packages/{non-core => deprecated}/jquery-waypoints/README.md (100%) rename packages/{non-core => deprecated}/jquery-waypoints/package.js (100%) rename packages/{non-core => deprecated}/jquery-waypoints/waypoints.coffee (100%) rename packages/{non-core => deprecated}/spiderable/.gitignore (100%) rename packages/{non-core => deprecated}/spiderable/README.md (100%) rename packages/{non-core => deprecated}/spiderable/package.js (100%) rename packages/{non-core => deprecated}/spiderable/phantom_script.js (100%) rename packages/{non-core => deprecated}/spiderable/spiderable.html (100%) rename packages/{non-core => deprecated}/spiderable/spiderable.js (100%) rename packages/{non-core => deprecated}/spiderable/spiderable_client.js (100%) rename packages/{non-core => deprecated}/spiderable/spiderable_client_tests.js (100%) rename packages/{non-core => deprecated}/spiderable/spiderable_server.js (100%) rename packages/{non-core => deprecated}/spiderable/spiderable_server_tests.js (100%) diff --git a/packages/non-core/jquery-history/.gitignore b/packages/deprecated/jquery-history/.gitignore similarity index 100% rename from packages/non-core/jquery-history/.gitignore rename to packages/deprecated/jquery-history/.gitignore diff --git a/packages/non-core/jquery-history/history.adapter.jquery.js b/packages/deprecated/jquery-history/history.adapter.jquery.js similarity index 100% rename from packages/non-core/jquery-history/history.adapter.jquery.js rename to packages/deprecated/jquery-history/history.adapter.jquery.js diff --git a/packages/non-core/jquery-history/history.html4.js b/packages/deprecated/jquery-history/history.html4.js similarity index 100% rename from packages/non-core/jquery-history/history.html4.js rename to packages/deprecated/jquery-history/history.html4.js diff --git a/packages/non-core/jquery-history/history.js b/packages/deprecated/jquery-history/history.js similarity index 100% rename from packages/non-core/jquery-history/history.js rename to packages/deprecated/jquery-history/history.js diff --git a/packages/non-core/jquery-history/package.js b/packages/deprecated/jquery-history/package.js similarity index 100% rename from packages/non-core/jquery-history/package.js rename to packages/deprecated/jquery-history/package.js diff --git a/packages/non-core/jquery-layout/.gitignore b/packages/deprecated/jquery-layout/.gitignore similarity index 100% rename from packages/non-core/jquery-layout/.gitignore rename to packages/deprecated/jquery-layout/.gitignore diff --git a/packages/non-core/jquery-layout/jquery.layout.js b/packages/deprecated/jquery-layout/jquery.layout.js similarity index 100% rename from packages/non-core/jquery-layout/jquery.layout.js rename to packages/deprecated/jquery-layout/jquery.layout.js diff --git a/packages/non-core/jquery-layout/package.js b/packages/deprecated/jquery-layout/package.js similarity index 100% rename from packages/non-core/jquery-layout/package.js rename to packages/deprecated/jquery-layout/package.js diff --git a/packages/non-core/jquery-waypoints/.gitignore b/packages/deprecated/jquery-waypoints/.gitignore similarity index 100% rename from packages/non-core/jquery-waypoints/.gitignore rename to packages/deprecated/jquery-waypoints/.gitignore diff --git a/packages/non-core/jquery-waypoints/README.md b/packages/deprecated/jquery-waypoints/README.md similarity index 100% rename from packages/non-core/jquery-waypoints/README.md rename to packages/deprecated/jquery-waypoints/README.md diff --git a/packages/non-core/jquery-waypoints/package.js b/packages/deprecated/jquery-waypoints/package.js similarity index 100% rename from packages/non-core/jquery-waypoints/package.js rename to packages/deprecated/jquery-waypoints/package.js diff --git a/packages/non-core/jquery-waypoints/waypoints.coffee b/packages/deprecated/jquery-waypoints/waypoints.coffee similarity index 100% rename from packages/non-core/jquery-waypoints/waypoints.coffee rename to packages/deprecated/jquery-waypoints/waypoints.coffee diff --git a/packages/non-core/spiderable/.gitignore b/packages/deprecated/spiderable/.gitignore similarity index 100% rename from packages/non-core/spiderable/.gitignore rename to packages/deprecated/spiderable/.gitignore diff --git a/packages/non-core/spiderable/README.md b/packages/deprecated/spiderable/README.md similarity index 100% rename from packages/non-core/spiderable/README.md rename to packages/deprecated/spiderable/README.md diff --git a/packages/non-core/spiderable/package.js b/packages/deprecated/spiderable/package.js similarity index 100% rename from packages/non-core/spiderable/package.js rename to packages/deprecated/spiderable/package.js diff --git a/packages/non-core/spiderable/phantom_script.js b/packages/deprecated/spiderable/phantom_script.js similarity index 100% rename from packages/non-core/spiderable/phantom_script.js rename to packages/deprecated/spiderable/phantom_script.js diff --git a/packages/non-core/spiderable/spiderable.html b/packages/deprecated/spiderable/spiderable.html similarity index 100% rename from packages/non-core/spiderable/spiderable.html rename to packages/deprecated/spiderable/spiderable.html diff --git a/packages/non-core/spiderable/spiderable.js b/packages/deprecated/spiderable/spiderable.js similarity index 100% rename from packages/non-core/spiderable/spiderable.js rename to packages/deprecated/spiderable/spiderable.js diff --git a/packages/non-core/spiderable/spiderable_client.js b/packages/deprecated/spiderable/spiderable_client.js similarity index 100% rename from packages/non-core/spiderable/spiderable_client.js rename to packages/deprecated/spiderable/spiderable_client.js diff --git a/packages/non-core/spiderable/spiderable_client_tests.js b/packages/deprecated/spiderable/spiderable_client_tests.js similarity index 100% rename from packages/non-core/spiderable/spiderable_client_tests.js rename to packages/deprecated/spiderable/spiderable_client_tests.js diff --git a/packages/non-core/spiderable/spiderable_server.js b/packages/deprecated/spiderable/spiderable_server.js similarity index 100% rename from packages/non-core/spiderable/spiderable_server.js rename to packages/deprecated/spiderable/spiderable_server.js diff --git a/packages/non-core/spiderable/spiderable_server_tests.js b/packages/deprecated/spiderable/spiderable_server_tests.js similarity index 100% rename from packages/non-core/spiderable/spiderable_server_tests.js rename to packages/deprecated/spiderable/spiderable_server_tests.js From f52b1726a5b1eb49b7f982a07b0f666545659fc5 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 13:39:02 -0400 Subject: [PATCH 29/53] Scan packages/non-core packages as local packages. Fixes #8961 and #8962. --- tools/project-context.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/project-context.js b/tools/project-context.js index 9d6025004a..971041f68e 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -651,7 +651,11 @@ _.extend(ProjectContext.prototype, { files.pathJoin(files.getCurrentToolsDir(), 'packages'); searchDirs.push( + // Include packages like packages/ecmascript. packagesDir, + // Include packages like packages/non-core/coffeescript. + files.pathJoin(packagesDir, "non-core"), + // Include packages like packages/non-core/blaze/packages/blaze. files.pathJoin(packagesDir, "non-core", "*", "packages"), ); } From 7f062192e1f9e4cb669d378b1140da9a33b0db42 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 31 Jul 2017 16:23:31 -0400 Subject: [PATCH 30/53] Eliminate underscore usage from the `meteor` package. --- packages/meteor/dynamics_browser.js | 38 ++-- packages/meteor/dynamics_nodejs.js | 83 +++++---- packages/meteor/fiber_helpers.js | 200 ++++++++++---------- packages/meteor/fiber_helpers_test.js | 6 +- packages/meteor/fiber_stubs_client.js | 130 ++++++------- packages/meteor/helpers.js | 258 +++++++++++++------------- packages/meteor/package.js | 2 - packages/meteor/timers.js | 110 ++++++----- packages/meteor/url_common.js | 2 +- packages/meteor/url_tests.js | 3 +- 10 files changed, 418 insertions(+), 414 deletions(-) diff --git a/packages/meteor/dynamics_browser.js b/packages/meteor/dynamics_browser.js index 4700f1f87d..61b525a5cb 100644 --- a/packages/meteor/dynamics_browser.js +++ b/packages/meteor/dynamics_browser.js @@ -7,32 +7,32 @@ Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; -_.extend(Meteor.EnvironmentVariable.prototype, { - get: function () { - return currentValues[this.slot]; - }, +var EVp = Meteor.EnvironmentVariable.prototype; - getOrNullIfOutsideFiber: function () { - return this.get(); - }, +EVp.get = function () { + return currentValues[this.slot]; +}; - withValue: function (value, func) { - var saved = currentValues[this.slot]; - try { - currentValues[this.slot] = value; - var ret = func(); - } finally { - currentValues[this.slot] = saved; - } - return ret; +EVp.getOrNullIfOutsideFiber = function () { + return this.get(); +}; + +EVp.withValue = function (value, func) { + var saved = currentValues[this.slot]; + try { + currentValues[this.slot] = value; + var ret = func(); + } finally { + currentValues[this.slot] = saved; } -}); + return ret; +}; Meteor.bindEnvironment = function (func, onException, _this) { // needed in order to be able to create closures inside func and // have the closed variables not change back to their original // values - var boundValues = _.clone(currentValues); + var boundValues = currentValues.slice(); if (!onException || typeof(onException) === 'string') { var description = onException || "callback of async function"; @@ -48,7 +48,7 @@ Meteor.bindEnvironment = function (func, onException, _this) { var savedValues = currentValues; try { currentValues = boundValues; - var ret = func.apply(_this, _.toArray(arguments)); + var ret = func.apply(_this, arguments); } catch (e) { // note: callback-hook currently relies on the fact that if onException // throws in the browser, the wrapped call throws. diff --git a/packages/meteor/dynamics_nodejs.js b/packages/meteor/dynamics_nodejs.js index 1037be8e38..5171e0d1dc 100644 --- a/packages/meteor/dynamics_nodejs.js +++ b/packages/meteor/dynamics_nodejs.js @@ -16,51 +16,51 @@ Meteor.EnvironmentVariable = function () { this.slot = nextSlot++; }; -_.extend(Meteor.EnvironmentVariable.prototype, { - get: function () { - Meteor._nodeCodeMustBeInFiber(); +var EVp = Meteor.EnvironmentVariable.prototype; - return Fiber.current._meteor_dynamics && - Fiber.current._meteor_dynamics[this.slot]; - }, +EVp.get = function () { + Meteor._nodeCodeMustBeInFiber(); - // Most Meteor code ought to run inside a fiber, and the - // _nodeCodeMustBeInFiber assertion helps you remember to include appropriate - // bindEnvironment calls (which will get you the *right value* for your - // environment variables, on the server). - // - // In some very special cases, it's more important to run Meteor code on the - // server in non-Fiber contexts rather than to strongly enforce the safeguard - // against forgetting to use bindEnvironment. For example, using `check` in - // some top-level constructs like connect handlers without needing unnecessary - // Fibers on every request is more important that possibly failing to find the - // correct argumentChecker. So this function is just like get(), but it - // returns null rather than throwing when called from outside a Fiber. (On the - // client, it is identical to get().) - getOrNullIfOutsideFiber: function () { - if (!Fiber.current) - return null; - return this.get(); - }, + return Fiber.current._meteor_dynamics && + Fiber.current._meteor_dynamics[this.slot]; +}; - withValue: function (value, func) { - Meteor._nodeCodeMustBeInFiber(); +// Most Meteor code ought to run inside a fiber, and the +// _nodeCodeMustBeInFiber assertion helps you remember to include appropriate +// bindEnvironment calls (which will get you the *right value* for your +// environment variables, on the server). +// +// In some very special cases, it's more important to run Meteor code on the +// server in non-Fiber contexts rather than to strongly enforce the safeguard +// against forgetting to use bindEnvironment. For example, using `check` in +// some top-level constructs like connect handlers without needing unnecessary +// Fibers on every request is more important that possibly failing to find the +// correct argumentChecker. So this function is just like get(), but it +// returns null rather than throwing when called from outside a Fiber. (On the +// client, it is identical to get().) +EVp.getOrNullIfOutsideFiber = function () { + if (!Fiber.current) + return null; + return this.get(); +}; - if (!Fiber.current._meteor_dynamics) - Fiber.current._meteor_dynamics = []; - var currentValues = Fiber.current._meteor_dynamics; +EVp.withValue = function (value, func) { + Meteor._nodeCodeMustBeInFiber(); - var saved = currentValues[this.slot]; - try { - currentValues[this.slot] = value; - var ret = func(); - } finally { - currentValues[this.slot] = saved; - } + if (!Fiber.current._meteor_dynamics) + Fiber.current._meteor_dynamics = []; + var currentValues = Fiber.current._meteor_dynamics; - return ret; + var saved = currentValues[this.slot]; + try { + currentValues[this.slot] = value; + var ret = func(); + } finally { + currentValues[this.slot] = saved; } -}); + + return ret; +}; // Meteor application code is always supposed to be run inside a // fiber. bindEnvironment ensures that the function it wraps is run from @@ -84,7 +84,8 @@ _.extend(Meteor.EnvironmentVariable.prototype, { Meteor.bindEnvironment = function (func, onException, _this) { Meteor._nodeCodeMustBeInFiber(); - var boundValues = _.clone(Fiber.current._meteor_dynamics || []); + var dynamics = Fiber.current._meteor_dynamics; + var boundValues = dynamics ? dynamics.slice() : []; if (!onException || typeof(onException) === 'string') { var description = onException || "callback of async function"; @@ -99,14 +100,14 @@ Meteor.bindEnvironment = function (func, onException, _this) { } return function (/* arguments */) { - var args = _.toArray(arguments); + var args = Array.prototype.slice.call(arguments); var runWithEnvironment = function () { var savedValues = Fiber.current._meteor_dynamics; try { // Need to clone boundValues in case two fibers invoke this // function at the same time - Fiber.current._meteor_dynamics = _.clone(boundValues); + Fiber.current._meteor_dynamics = boundValues.slice(); var ret = func.apply(_this, args); } catch (e) { // note: callback-hook currently relies on the fact that if onException diff --git a/packages/meteor/fiber_helpers.js b/packages/meteor/fiber_helpers.js index abcc364f33..c6bf63b945 100644 --- a/packages/meteor/fiber_helpers.js +++ b/packages/meteor/fiber_helpers.js @@ -54,121 +54,123 @@ Meteor._SynchronousQueue = function () { self._draining = false; }; -_.extend(Meteor._SynchronousQueue.prototype, { - runTask: function (task) { - var self = this; +var SQp = Meteor._SynchronousQueue.prototype; - if (!self.safeToRunTask()) { - if (Fiber.current) - throw new Error("Can't runTask from another task in the same fiber"); - else - throw new Error("Can only call runTask in a Fiber"); - } +SQp.runTask = function (task) { + var self = this; - var fut = new Future; - var handle = { - task: Meteor.bindEnvironment(task, function (e) { - Meteor._debug("Exception from task:", e && e.stack || e); - throw e; - }), - future: fut, - name: task.name - }; - self._taskHandles.push(handle); - self._scheduleRun(); - // Yield. We'll get back here after the task is run (and will throw if the - // task throws). - fut.wait(); - }, - queueTask: function (task) { - var self = this; - self._taskHandles.push({ - task: task, - name: task.name - }); - self._scheduleRun(); - // No need to block. - }, + if (!self.safeToRunTask()) { + if (Fiber.current) + throw new Error("Can't runTask from another task in the same fiber"); + else + throw new Error("Can only call runTask in a Fiber"); + } - flush: function () { - var self = this; - self.runTask(function () {}); - }, + var fut = new Future; + var handle = { + task: Meteor.bindEnvironment(task, function (e) { + Meteor._debug("Exception from task:", e && e.stack || e); + throw e; + }), + future: fut, + name: task.name + }; + self._taskHandles.push(handle); + self._scheduleRun(); + // Yield. We'll get back here after the task is run (and will throw if the + // task throws). + fut.wait(); +}; - safeToRunTask: function () { - var self = this; - return Fiber.current && self._currentTaskFiber !== Fiber.current; - }, +SQp.queueTask = function (task) { + var self = this; + self._taskHandles.push({ + task: task, + name: task.name + }); + self._scheduleRun(); + // No need to block. +}; - drain: function () { - var self = this; - if (self._draining) - return; - if (!self.safeToRunTask()) - return; - self._draining = true; - while (! self._taskHandles.isEmpty()) { - self.flush(); - } - self._draining = false; - }, +SQp.flush = function () { + var self = this; + self.runTask(function () {}); +}; - _scheduleRun: function () { - var self = this; - // Already running or scheduled? Do nothing. - if (self._runningOrRunScheduled) - return; +SQp.safeToRunTask = function () { + var self = this; + return Fiber.current && self._currentTaskFiber !== Fiber.current; +}; - self._runningOrRunScheduled = true; - setImmediate(function () { - Fiber(function () { - self._run(); - }).run(); - }); - }, - _run: function () { - var self = this; +SQp.drain = function () { + var self = this; + if (self._draining) + return; + if (!self.safeToRunTask()) + return; + self._draining = true; + while (! self._taskHandles.isEmpty()) { + self.flush(); + } + self._draining = false; +}; - if (!self._runningOrRunScheduled) - throw new Error("expected to be _runningOrRunScheduled"); +SQp._scheduleRun = function () { + var self = this; + // Already running or scheduled? Do nothing. + if (self._runningOrRunScheduled) + return; - if (self._taskHandles.isEmpty()) { - // Done running tasks! Don't immediately schedule another run, but - // allow future tasks to do so. - self._runningOrRunScheduled = false; - return; - } - var taskHandle = self._taskHandles.shift(); + self._runningOrRunScheduled = true; + setImmediate(function () { + Fiber(function () { + self._run(); + }).run(); + }); +}; - // Run the task. - self._currentTaskFiber = Fiber.current; - var exception = undefined; - try { - taskHandle.task(); - } catch (err) { - if (taskHandle.future) { - // We'll throw this exception through runTask. - exception = err; - } else { - Meteor._debug("Exception in queued task: " + (err.stack || err)); - } - } - self._currentTaskFiber = undefined; +SQp._run = function () { + var self = this; - // Soon, run the next task, if there is any. + if (!self._runningOrRunScheduled) + throw new Error("expected to be _runningOrRunScheduled"); + + if (self._taskHandles.isEmpty()) { + // Done running tasks! Don't immediately schedule another run, but + // allow future tasks to do so. self._runningOrRunScheduled = false; - self._scheduleRun(); + return; + } + var taskHandle = self._taskHandles.shift(); - // If this was queued with runTask, let the runTask call return (throwing if - // the task threw). + // Run the task. + self._currentTaskFiber = Fiber.current; + var exception = undefined; + try { + taskHandle.task(); + } catch (err) { if (taskHandle.future) { - if (exception) - taskHandle.future['throw'](exception); - else - taskHandle.future['return'](); + // We'll throw this exception through runTask. + exception = err; + } else { + Meteor._debug("Exception in queued task: " + (err.stack || err)); } } -}); + self._currentTaskFiber = undefined; + + // Soon, run the next task, if there is any. + self._runningOrRunScheduled = false; + self._scheduleRun(); + + // If this was queued with runTask, let the runTask call return (throwing if + // the task threw). + if (taskHandle.future) { + if (exception) + taskHandle.future['throw'](exception); + else + taskHandle.future['return'](); + } +}; // Sleep. Mostly used for debugging (eg, inserting latency into server // methods). diff --git a/packages/meteor/fiber_helpers_test.js b/packages/meteor/fiber_helpers_test.js index f2fede7559..84093ee1d7 100644 --- a/packages/meteor/fiber_helpers_test.js +++ b/packages/meteor/fiber_helpers_test.js @@ -9,7 +9,11 @@ Tinytest.add("fibers - synchronous queue", function (test) { }; }; var outputIsUpTo = function (n) { - test.equal(output, _.range(1, n+1)); + var range = []; + for (var i = 1; i <= n; ++i) { + range.push(i); + } + test.equal(output, range); }; // Queue a task. It cannot run until we yield. diff --git a/packages/meteor/fiber_stubs_client.js b/packages/meteor/fiber_stubs_client.js index 4e4cdecde8..ac7a98528e 100644 --- a/packages/meteor/fiber_stubs_client.js +++ b/packages/meteor/fiber_stubs_client.js @@ -17,70 +17,72 @@ Meteor._SynchronousQueue = function () { self._runTimeout = null; }; -_.extend(Meteor._SynchronousQueue.prototype, { - runTask: function (task) { - var self = this; - if (!self.safeToRunTask()) - throw new Error("Could not synchronously run a task from a running task"); - self._tasks.push(task); - var tasks = self._tasks; - self._tasks = []; - self._running = true; +var SQp = Meteor._SynchronousQueue.prototype; - if (self._runTimeout) { - // Since we're going to drain the queue, we can forget about the timeout - // which tries to run it. (But if one of our tasks queues something else, - // the timeout will be correctly re-created.) - clearTimeout(self._runTimeout); - self._runTimeout = null; - } +SQp.runTask = function (task) { + var self = this; + if (!self.safeToRunTask()) + throw new Error("Could not synchronously run a task from a running task"); + self._tasks.push(task); + var tasks = self._tasks; + self._tasks = []; + self._running = true; - try { - while (!_.isEmpty(tasks)) { - var t = tasks.shift(); - try { - t(); - } catch (e) { - if (_.isEmpty(tasks)) { - // this was the last task, that is, the one we're calling runTask - // for. - throw e; - } else { - Meteor._debug("Exception in queued task: " + (e.stack || e)); - } - } - } - } finally { - self._running = false; - } - }, - - queueTask: function (task) { - var self = this; - self._tasks.push(task); - // Intentionally not using Meteor.setTimeout, because it doesn't like runing - // in stubs for now. - if (!self._runTimeout) { - self._runTimeout = setTimeout(_.bind(self.flush, self), 0); - } - }, - - flush: function () { - var self = this; - self.runTask(function () {}); - }, - - drain: function () { - var self = this; - if (!self.safeToRunTask()) - return; - while (!_.isEmpty(self._tasks)) { - self.flush(); - } - }, - - safeToRunTask: function () { - var self = this; - return !self._running; + if (self._runTimeout) { + // Since we're going to drain the queue, we can forget about the timeout + // which tries to run it. (But if one of our tasks queues something else, + // the timeout will be correctly re-created.) + clearTimeout(self._runTimeout); + self._runTimeout = null; } -}); + + try { + while (tasks.length > 0) { + var t = tasks.shift(); + try { + t(); + } catch (e) { + if (tasks.length === 0) { + // this was the last task, that is, the one we're calling runTask + // for. + throw e; + } + Meteor._debug("Exception in queued task: " + (e.stack || e)); + } + } + } finally { + self._running = false; + } +}; + +SQp.queueTask = function (task) { + var self = this; + self._tasks.push(task); + // Intentionally not using Meteor.setTimeout, because it doesn't like runing + // in stubs for now. + if (!self._runTimeout) { + self._runTimeout = setTimeout(function () { + return self.flush.apply(self, arguments); + }, 0); + } +}; + +SQp.flush = function () { + var self = this; + self.runTask(function () {}); +}; + +SQp.drain = function () { + var self = this; + if (!self.safeToRunTask()) { + return; + } + while (self._tasks.length > 0) { + self.flush(); + } +}; + +SQp.safeToRunTask = function () { + var self = this; + return !self._running; +}; diff --git a/packages/meteor/helpers.js b/packages/meteor/helpers.js index 7e59309a54..38ce106b4d 100644 --- a/packages/meteor/helpers.js +++ b/packages/meteor/helpers.js @@ -14,136 +14,136 @@ if (typeof __meteor_runtime_config__ === 'object' && // XXX find a better home for these? Ideally they would be _.get, // _.ensure, _.delete.. -_.extend(Meteor, { - // _get(a,b,c,d) returns a[b][c][d], or else undefined if a[b] or - // a[b][c] doesn't exist. - // - _get: function (obj /*, arguments */) { - for (var i = 1; i < arguments.length; i++) { - if (!(arguments[i] in obj)) - return undefined; - obj = obj[arguments[i]]; - } - return obj; - }, - - // _ensure(a,b,c,d) ensures that a[b][c][d] exists. If it does not, - // it is created and set to {}. Either way, it is returned. - // - _ensure: function (obj /*, arguments */) { - for (var i = 1; i < arguments.length; i++) { - var key = arguments[i]; - if (!(key in obj)) - obj[key] = {}; - obj = obj[key]; - } - - return obj; - }, - - // _delete(a, b, c, d) deletes a[b][c][d], then a[b][c] unless it - // isn't empty, then a[b] unless it isn't empty. - // - _delete: function (obj /*, arguments */) { - var stack = [obj]; - var leaf = true; - for (var i = 1; i < arguments.length - 1; i++) { - var key = arguments[i]; - if (!(key in obj)) { - leaf = false; - break; - } - obj = obj[key]; - if (typeof obj !== "object") - break; - stack.push(obj); - } - - for (var i = stack.length - 1; i >= 0; i--) { - var key = arguments[i+1]; - - if (leaf) - leaf = false; - else - for (var other in stack[i][key]) - return; // not empty -- we're done - - delete stack[i][key]; - } - }, - - // wrapAsync can wrap any function that takes some number of arguments that - // can't be undefined, followed by some optional arguments, where the callback - // is the last optional argument. - // e.g. fs.readFile(pathname, [callback]), - // fs.open(pathname, flags, [mode], [callback]) - // For maximum effectiveness and least confusion, wrapAsync should be used on - // functions where the callback is the only argument of type Function. - - /** - * @memberOf Meteor - * @summary Wrap a function that takes a callback function as its final parameter. The signature of the callback of the wrapped function should be `function(error, result){}`. On the server, the wrapped function can be used either synchronously (without passing a callback) or asynchronously (when a callback is passed). On the client, a callback is always required; errors will be logged if there is no callback. If a callback is provided, the environment captured when the original function was called will be restored in the callback. - * @locus Anywhere - * @param {Function} func A function that takes a callback as its final parameter - * @param {Object} [context] Optional `this` object against which the original function will be invoked - */ - wrapAsync: function (fn, context) { - return function (/* arguments */) { - var self = context || this; - var newArgs = _.toArray(arguments); - var callback; - - for (var i = newArgs.length - 1; i >= 0; --i) { - var arg = newArgs[i]; - var type = typeof arg; - if (type !== "undefined") { - if (type === "function") { - callback = arg; - } - break; - } - } - - if (! callback) { - if (Meteor.isClient) { - callback = logErr; - } else { - var fut = new Future(); - callback = fut.resolver(); - } - ++i; // Insert the callback just after arg. - } - - newArgs[i] = Meteor.bindEnvironment(callback); - var result = fn.apply(self, newArgs); - return fut ? fut.wait() : result; - }; - }, - - // Sets child's prototype to a new object whose prototype is parent's - // prototype. Used as: - // Meteor._inherits(ClassB, ClassA). - // _.extend(ClassB.prototype, { ... }) - // Inspired by CoffeeScript's `extend` and Google Closure's `goog.inherits`. - _inherits: function (Child, Parent) { - // copy Parent static properties - for (var key in Parent) { - // make sure we only copy hasOwnProperty properties vs. prototype - // properties - if (_.has(Parent, key)) - Child[key] = Parent[key]; - } - - // a middle member of prototype chain: takes the prototype from the Parent - var Middle = function () { - this.constructor = Child; - }; - Middle.prototype = Parent.prototype; - Child.prototype = new Middle(); - Child.__super__ = Parent.prototype; - return Child; +// _get(a,b,c,d) returns a[b][c][d], or else undefined if a[b] or +// a[b][c] doesn't exist. +// +Meteor._get = function (obj /*, arguments */) { + for (var i = 1; i < arguments.length; i++) { + if (!(arguments[i] in obj)) + return undefined; + obj = obj[arguments[i]]; } -}); + return obj; +}; + +// _ensure(a,b,c,d) ensures that a[b][c][d] exists. If it does not, +// it is created and set to {}. Either way, it is returned. +// +Meteor._ensure = function (obj /*, arguments */) { + for (var i = 1; i < arguments.length; i++) { + var key = arguments[i]; + if (!(key in obj)) + obj[key] = {}; + obj = obj[key]; + } + + return obj; +}; + +// _delete(a, b, c, d) deletes a[b][c][d], then a[b][c] unless it +// isn't empty, then a[b] unless it isn't empty. +// +Meteor._delete = function (obj /*, arguments */) { + var stack = [obj]; + var leaf = true; + for (var i = 1; i < arguments.length - 1; i++) { + var key = arguments[i]; + if (!(key in obj)) { + leaf = false; + break; + } + obj = obj[key]; + if (typeof obj !== "object") + break; + stack.push(obj); + } + + for (var i = stack.length - 1; i >= 0; i--) { + var key = arguments[i+1]; + + if (leaf) + leaf = false; + else + for (var other in stack[i][key]) + return; // not empty -- we're done + + delete stack[i][key]; + } +}; + +// wrapAsync can wrap any function that takes some number of arguments that +// can't be undefined, followed by some optional arguments, where the callback +// is the last optional argument. +// e.g. fs.readFile(pathname, [callback]), +// fs.open(pathname, flags, [mode], [callback]) +// For maximum effectiveness and least confusion, wrapAsync should be used on +// functions where the callback is the only argument of type Function. + +/** + * @memberOf Meteor + * @summary Wrap a function that takes a callback function as its final parameter. The signature of the callback of the wrapped function should be `function(error, result){}`. On the server, the wrapped function can be used either synchronously (without passing a callback) or asynchronously (when a callback is passed). On the client, a callback is always required; errors will be logged if there is no callback. If a callback is provided, the environment captured when the original function was called will be restored in the callback. + * @locus Anywhere + * @param {Function} func A function that takes a callback as its final parameter + * @param {Object} [context] Optional `this` object against which the original function will be invoked + */ +Meteor.wrapAsync = function (fn, context) { + return function (/* arguments */) { + var self = context || this; + var newArgs = Array.prototype.slice.call(arguments); + var callback; + + for (var i = newArgs.length - 1; i >= 0; --i) { + var arg = newArgs[i]; + var type = typeof arg; + if (type !== "undefined") { + if (type === "function") { + callback = arg; + } + break; + } + } + + if (! callback) { + if (Meteor.isClient) { + callback = logErr; + } else { + var fut = new Future(); + callback = fut.resolver(); + } + ++i; // Insert the callback just after arg. + } + + newArgs[i] = Meteor.bindEnvironment(callback); + var result = fn.apply(self, newArgs); + return fut ? fut.wait() : result; + }; +}; + +// Sets child's prototype to a new object whose prototype is parent's +// prototype. Used as: +// Meteor._inherits(ClassB, ClassA). +// _.extend(ClassB.prototype, { ... }) +// Inspired by CoffeeScript's `extend` and Google Closure's `goog.inherits`. +var hasOwn = Object.prototype.hasOwnProperty; +Meteor._inherits = function (Child, Parent) { + // copy Parent static properties + for (var key in Parent) { + // make sure we only copy hasOwnProperty properties vs. prototype + // properties + if (hasOwn.call(Parent, key)) { + Child[key] = Parent[key]; + } + } + + // a middle member of prototype chain: takes the prototype from the Parent + var Middle = function () { + this.constructor = Child; + }; + Middle.prototype = Parent.prototype; + Child.prototype = new Middle(); + Child.__super__ = Parent.prototype; + return Child; +}; var warnedAboutWrapAsync = false; diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 2626202ecf..2f82713ca4 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -15,8 +15,6 @@ Npm.depends({ }); Package.onUse(function (api) { - api.use('underscore', ['client', 'server']); - api.use('isobuild:compiler-plugin@1.0.0'); api.export('Meteor'); diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index 1efdb17f36..31ba94749b 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -13,65 +13,63 @@ var bindAndCatch = function (context, f) { return Meteor.bindEnvironment(withoutInvocation(f), context); }; -_.extend(Meteor, { - // Meteor.setTimeout and Meteor.setInterval callbacks scheduled - // inside a server method are not part of the method invocation and - // should clear out the CurrentMethodInvocation environment variable. +// Meteor.setTimeout and Meteor.setInterval callbacks scheduled +// inside a server method are not part of the method invocation and +// should clear out the CurrentMethodInvocation environment variable. - /** - * @memberOf Meteor - * @summary Call a function in the future after waiting for a specified delay. - * @locus Anywhere - * @param {Function} func The function to run - * @param {Number} delay Number of milliseconds to wait before calling function - */ - setTimeout: function (f, duration) { - return setTimeout(bindAndCatch("setTimeout callback", f), duration); - }, +/** + * @memberOf Meteor + * @summary Call a function in the future after waiting for a specified delay. + * @locus Anywhere + * @param {Function} func The function to run + * @param {Number} delay Number of milliseconds to wait before calling function + */ +Meteor.setTimeout = function (f, duration) { + return setTimeout(bindAndCatch("setTimeout callback", f), duration); +}; - /** - * @memberOf Meteor - * @summary Call a function repeatedly, with a time delay between calls. - * @locus Anywhere - * @param {Function} func The function to run - * @param {Number} delay Number of milliseconds to wait between each function call. - */ - setInterval: function (f, duration) { - return setInterval(bindAndCatch("setInterval callback", f), duration); - }, +/** + * @memberOf Meteor + * @summary Call a function repeatedly, with a time delay between calls. + * @locus Anywhere + * @param {Function} func The function to run + * @param {Number} delay Number of milliseconds to wait between each function call. + */ +Meteor.setInterval = function (f, duration) { + return setInterval(bindAndCatch("setInterval callback", f), duration); +}; - /** - * @memberOf Meteor - * @summary Cancel a repeating function call scheduled by `Meteor.setInterval`. - * @locus Anywhere - * @param {Object} id The handle returned by `Meteor.setInterval` - */ - clearInterval: function(x) { - return clearInterval(x); - }, +/** + * @memberOf Meteor + * @summary Cancel a repeating function call scheduled by `Meteor.setInterval`. + * @locus Anywhere + * @param {Object} id The handle returned by `Meteor.setInterval` + */ +Meteor.clearInterval = function(x) { + return clearInterval(x); +}; - /** - * @memberOf Meteor - * @summary Cancel a function call scheduled by `Meteor.setTimeout`. - * @locus Anywhere - * @param {Object} id The handle returned by `Meteor.setTimeout` - */ - clearTimeout: function(x) { - return clearTimeout(x); - }, +/** + * @memberOf Meteor + * @summary Cancel a function call scheduled by `Meteor.setTimeout`. + * @locus Anywhere + * @param {Object} id The handle returned by `Meteor.setTimeout` + */ +Meteor.clearTimeout = function(x) { + return clearTimeout(x); +}; - // XXX consider making this guarantee ordering of defer'd callbacks, like - // Tracker.afterFlush or Node's nextTick (in practice). Then tests can do: - // callSomethingThatDefersSomeWork(); - // Meteor.defer(expect(somethingThatValidatesThatTheWorkHappened)); +// XXX consider making this guarantee ordering of defer'd callbacks, like +// Tracker.afterFlush or Node's nextTick (in practice). Then tests can do: +// callSomethingThatDefersSomeWork(); +// Meteor.defer(expect(somethingThatValidatesThatTheWorkHappened)); - /** - * @memberOf Meteor - * @summary Defer execution of a function to run asynchronously in the background (similar to `Meteor.setTimeout(func, 0)`. - * @locus Anywhere - * @param {Function} func The function to run - */ - defer: function (f) { - Meteor._setImmediate(bindAndCatch("defer callback", f)); - } -}); +/** + * @memberOf Meteor + * @summary Defer execution of a function to run asynchronously in the background (similar to `Meteor.setTimeout(func, 0)`. + * @locus Anywhere + * @param {Function} func The function to run + */ +Meteor.defer = function (f) { + Meteor._setImmediate(bindAndCatch("defer callback", f)); +}; diff --git a/packages/meteor/url_common.js b/packages/meteor/url_common.js index f1124c2c7d..f90453dfc0 100644 --- a/packages/meteor/url_common.js +++ b/packages/meteor/url_common.js @@ -14,7 +14,7 @@ Meteor.absoluteUrl = function (path, options) { path = undefined; } // merge options with defaults - options = _.extend({}, Meteor.absoluteUrl.defaultOptions, options || {}); + options = Object.assign({}, Meteor.absoluteUrl.defaultOptions, options || {}); var url = options.rootUrl; if (!url) diff --git a/packages/meteor/url_tests.js b/packages/meteor/url_tests.js index 002c5d4f4f..ad686ceb16 100644 --- a/packages/meteor/url_tests.js +++ b/packages/meteor/url_tests.js @@ -1,6 +1,5 @@ Tinytest.add("absolute-url - basics", function(test) { - - _.each(['', 'http://'], function (prefix) { + ['', 'http://'].forEach(function (prefix) { test.equal(Meteor.absoluteUrl({rootUrl: prefix + 'asdf.com'}), 'http://asdf.com/'); From 88824ca04d637583efec2b45570bda0a3e7da6cc Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 14:37:26 -0400 Subject: [PATCH 31/53] Guard against nonexistent Package.ddp.DDP._CurrentMethodInvocation. As suggested by @cwholman in this comment: https://github.com/meteor/meteor/issues/8947#issuecomment-318393234 Fixes #8947. --- packages/meteor/package.js | 2 +- packages/meteor/timers.js | 33 ++++++++++++++++++++++----------- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/packages/meteor/package.js b/packages/meteor/package.js index 2f82713ca4..05200948ee 100644 --- a/packages/meteor/package.js +++ b/packages/meteor/package.js @@ -2,7 +2,7 @@ Package.describe({ summary: "Core Meteor environment", - version: '1.7.0' + version: '1.7.1' }); Package.registerBuildPlugin({ diff --git a/packages/meteor/timers.js b/packages/meteor/timers.js index 31ba94749b..9b0596bfa1 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -1,17 +1,28 @@ -var withoutInvocation = function (f) { +function withoutInvocation(f) { if (Package.ddp) { - var _CurrentMethodInvocation = Package.ddp.DDP._CurrentMethodInvocation; - if (_CurrentMethodInvocation.get() && _CurrentMethodInvocation.get().isSimulation) - throw new Error("Can't set timers inside simulations"); - return function () { _CurrentMethodInvocation.withValue(null, f); }; - } - else - return f; -}; + var DDP = Package.ddp.DDP; + var CurrentInvocation = + DDP._CurrentMethodInvocation || + // For backwards compatibility, as explained in this issue: + // https://github.com/meteor/meteor/issues/8947 + DDP._CurrentInvocation; -var bindAndCatch = function (context, f) { + var invocation = CurrentInvocation.get(); + if (invocation && invocation.isSimulation) { + throw new Error("Can't set timers inside simulations"); + } + + return function () { + CurrentInvocation.withValue(null, f); + }; + } else { + return f; + } +} + +function bindAndCatch(context, f) { return Meteor.bindEnvironment(withoutInvocation(f), context); -}; +} // Meteor.setTimeout and Meteor.setInterval callbacks scheduled // inside a server method are not part of the method invocation and From 9a74c7cafa23d9a6698369e58a8e0842e55aab97 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Wed, 2 Aug 2017 21:57:34 +0300 Subject: [PATCH 32/53] Bump $BUNDLE_VERSION to 4.8.30 before rebuilding dev bundle. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index ef8749df65..dcbbf84a68 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.8.21 +BUNDLE_VERSION=4.8.30 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 82a6feeaf43b11766645c065f87bd7322c969ce9 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sun, 9 Jul 2017 21:39:12 -0400 Subject: [PATCH 33/53] Adjust EJSON API to handle objects with properties named "length" --- packages/ejson/ejson.js | 10 +++--- .../ejson/{ejson_test.js => ejson_tests.js} | 32 +++++++++++++++++++ packages/ejson/package.js | 15 ++++----- 3 files changed, 45 insertions(+), 12 deletions(-) rename packages/ejson/{ejson_test.js => ejson_tests.js} (88%) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index fd7a9d4929..1a4a1a623e 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -468,7 +468,7 @@ EJSON.equals = function (a, b, options) { i++; return true; }); - return ret && _.size(b) === i; + return ret && _.keys(b).length === i; } }; @@ -515,9 +515,11 @@ EJSON.clone = function (v) { } // handle other objects ret = {}; - _.each(v, function (value, key) { - ret[key] = EJSON.clone(value); - }); + var keys = _.keys(v); + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + ret[key] = EJSON.clone(v[key]); + } return ret; }; diff --git a/packages/ejson/ejson_test.js b/packages/ejson/ejson_tests.js similarity index 88% rename from packages/ejson/ejson_test.js rename to packages/ejson/ejson_tests.js index fe28c67380..f4d1952f86 100644 --- a/packages/ejson/ejson_test.js +++ b/packages/ejson/ejson_tests.js @@ -240,3 +240,35 @@ Tinytest.add("ejson - custom types", function (test) { clone.address.city = 'Sherbrooke'; test.notEqual( obj, clone ); }); + +// Verify objects with a property named "length" can be handled by the EJSON +// API properly (see https://github.com/meteor/meteor/issues/5175). +Tinytest.add( + "ejson - handle objects with properties named 'length'", + function (test) { + var Widget = function () { + this.length = 10; + }; + var widget = new Widget(); + + var toJsonWidget = EJSON.toJSONValue(widget); + test.equal(widget, toJsonWidget); + + var fromJsonWidget = EJSON.fromJSONValue(widget); + test.equal(widget, fromJsonWidget); + + var stringifiedWidget = EJSON.stringify(widget); + test.equal(stringifiedWidget, '{"length":10}'); + + var parsedWidget = EJSON.parse('{"length":10}'); + test.equal({ length: 10 }, parsedWidget); + + test.isFalse(EJSON.isBinary(widget)); + + var widget2 = new Widget(); + test.isTrue(widget, widget2); + + var clonedWidget = EJSON.clone(widget); + test.equal(widget, clonedWidget); + } +); diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 27c712f5a3..5fa9cb1e24 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -1,20 +1,19 @@ Package.describe({ summary: "Extended and Extensible JSON library", - version: '1.0.13' + version: '1.0.14' }); Package.onUse(function (api) { api.use(['underscore', 'base64']); + api.mainModule('ejson.js'); + api.addFiles('stringify.js'); api.export('EJSON'); - api.export('EJSONTest', {testOnly: true}); - api.addFiles('ejson.js', ['client', 'server']); - api.addFiles('stringify.js', ['client', 'server']); + api.export('EJSONTest', { testOnly: true }); }); Package.onTest(function (api) { - api.use('ejson', ['client', 'server']); api.use(['tinytest', 'underscore']); - - api.addFiles('custom_models_for_tests.js', ['client', 'server']); - api.addFiles('ejson_test.js', ['client', 'server']); + api.use('ejson'); + api.addFiles('custom_models_for_tests.js'); + api.mainModule('ejson_tests.js'); }); From c6bddb331a8fd8c24336c6a7232406a3f108a6b5 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sun, 16 Jul 2017 11:52:57 -0400 Subject: [PATCH 34/53] Remove underscore dependency. --- packages/ejson/custom_models_for_tests.js | 105 ++--- packages/ejson/ejson.js | 501 ++++++++++++---------- packages/ejson/ejson_tests.js | 268 ++++++------ packages/ejson/package.js | 15 +- packages/ejson/stringify.js | 97 +++-- 5 files changed, 522 insertions(+), 464 deletions(-) diff --git a/packages/ejson/custom_models_for_tests.js b/packages/ejson/custom_models_for_tests.js index 7c602c69bc..c60a6b3caf 100644 --- a/packages/ejson/custom_models_for_tests.js +++ b/packages/ejson/custom_models_for_tests.js @@ -1,87 +1,74 @@ -function Address (city, state) { - this.city = city; - this.state = state; -} +import { EJSON } from './ejson'; -Address.prototype = { - constructor: Address, +class Address { + constructor(city, state) { + this.city = city; + this.state = state; + } - typeName: function () { - return "Address"; - }, + typeName() { + return 'Address'; + } - toJSONValue: function () { + toJSONValue() { return { city: this.city, - state: this.state + state: this.state, }; } } -EJSON.addType("Address", function fromJSONValue(value) { - return new Address(value.city, value.state); -}); +EJSON.addType('Address', value => new Address(value.city, value.state)); -function Person (name, dob, address) { - this.name = name; - this.dob = dob; - this.address = address; -} +class Person { + constructor(name, dob, address) { + this.name = name; + this.dob = dob; + this.address = address; + } -Person.prototype = { - constructor: Person, + typeName() { + return 'Person'; + } - typeName: function () { - return "Person"; - }, - - toJSONValue: function () { + toJSONValue() { return { name: this.name, dob: EJSON.toJSONValue(this.dob), - address: EJSON.toJSONValue(this.address) + address: EJSON.toJSONValue(this.address), }; } } -_.extend(Person, { - fromJSONValue: function(value) { - return new Person( - value.name, - EJSON.fromJSONValue(value.dob), - EJSON.fromJSONValue(value.address) - ); +EJSON.addType( + 'Person', + value => new Person( + value.name, + EJSON.fromJSONValue(value.dob), + EJSON.fromJSONValue(value.address) + ) +); + +class Holder { + constructor(content) { + this.content = content; } -}); -EJSON.addType("Person", Person.fromJSONValue); + typeName() { + return 'Holder'; + } -function Holder (content) { - this.content = content; -} - -Holder.prototype = { - constructor: Holder, - - typeName: function () { - return "Holder"; - }, - - toJSONValue: function () { + toJSONValue() { return this.content; } } -_.extend(Holder, { - fromJSONValue: function(value) { - return new Holder(value); - } -}); +EJSON.addType('Holder', value => new Holder(value)); -EJSON.addType("Holder", Holder.fromJSONValue); +const EJSONTest = { + Address, + Person, + Holder, +}; -_.extend(EJSONTest, { - Address: Address, - Person: Person, - Holder: Holder -}); +export default EJSONTest; diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index 1a4a1a623e..b989e3b124 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -2,10 +2,7 @@ * @namespace * @summary Namespace for EJSON functions */ -EJSON = {}; -EJSONTest = {}; - - +const EJSON = {}; // Custom type interface definition /** @@ -19,7 +16,9 @@ EJSONTest = {}; /** * @function typeName * @memberOf EJSON.CustomType - * @summary Return the tag used to identify this type. This must match the tag used to register this type with [`EJSON.addType`](#ejson_add_type). + * @summary Return the tag used to identify this type. This must match the + * tag used to register this type with + * [`EJSON.addType`](#ejson_add_type). * @locus Anywhere * @instance */ @@ -35,7 +34,8 @@ EJSONTest = {}; /** * @function clone * @memberOf EJSON.CustomType - * @summary Return a value `r` such that `this.equals(r)` is true, and modifications to `r` do not affect `this` and vice versa. + * @summary Return a value `r` such that `this.equals(r)` is true, and + * modifications to `r` do not affect `this` and vice versa. * @locus Anywhere * @instance */ @@ -43,14 +43,22 @@ EJSONTest = {}; /** * @function equals * @memberOf EJSON.CustomType - * @summary Return `true` if `other` has a value equal to `this`; `false` otherwise. + * @summary Return `true` if `other` has a value equal to `this`; `false` + * otherwise. * @locus Anywhere * @param {Object} other Another object to compare this to. * @instance */ +const customTypes = {}; + +const hasOwn = (obj, prop) => ({}).hasOwnProperty.call(obj, prop); + +const isArguments = obj => obj != null && hasOwn(obj, 'callee'); + +const isInfOrNan = + obj => Number.isNaN(obj) || obj === Infinity || obj === -Infinity; -var customTypes = {}; // Add a custom type, using a method of your choice to get to and // from a basic JSON-able representation. The factory argument // is a function of JSON-able --> your object @@ -66,33 +74,35 @@ var customTypes = {}; /** * @summary Add a custom datatype to EJSON. * @locus Anywhere - * @param {String} name A tag for your custom type; must be unique among custom data types defined in your project, and must match the result of your type's `typeName` method. - * @param {Function} factory A function that deserializes a JSON-compatible value into an instance of your type. This should match the serialization performed by your type's `toJSONValue` method. + * @param {String} name A tag for your custom type; must be unique among + * custom data types defined in your project, and must + * match the result of your type's `typeName` method. + * @param {Function} factory A function that deserializes a JSON-compatible + * value into an instance of your type. This should + * match the serialization performed by your + * type's `toJSONValue` method. */ -EJSON.addType = function (name, factory) { - if (_.has(customTypes, name)) - throw new Error("Type " + name + " already present"); +EJSON.addType = (name, factory) => { + if (hasOwn(customTypes, name)) { + throw new Error(`Type ${name} already present`); + } customTypes[name] = factory; }; -var isInfOrNan = function (obj) { - return _.isNaN(obj) || obj === Infinity || obj === -Infinity; -}; - -var builtinConverters = [ +const builtinConverters = [ { // Date - matchJSONValue: function (obj) { - return _.has(obj, '$date') && _.size(obj) === 1; + matchJSONValue(obj) { + return hasOwn(obj, '$date') && Object.keys(obj).length === 1; }, - matchObject: function (obj) { + matchObject(obj) { return obj instanceof Date; }, - toJSONValue: function (obj) { + toJSONValue(obj) { return {$date: obj.getTime()}; }, - fromJSONValue: function (obj) { + fromJSONValue(obj) { return new Date(obj.$date); - } + }, }, { // RegExp matchJSONValue: function (obj) { @@ -114,127 +124,142 @@ var builtinConverters = [ }, { // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' // which we match.) - matchJSONValue: function (obj) { - return _.has(obj, '$InfNaN') && _.size(obj) === 1; + matchJSONValue(obj) { + return hasOwn(obj, '$InfNaN') && Object.keys(obj).length === 1; }, matchObject: isInfOrNan, - toJSONValue: function (obj) { - var sign; - if (_.isNaN(obj)) + toJSONValue(obj) { + let sign; + if (Number.isNaN(obj)) { sign = 0; - else if (obj === Infinity) + } else if (obj === Infinity) { sign = 1; - else + } else { sign = -1; + } return {$InfNaN: sign}; }, - fromJSONValue: function (obj) { - return obj.$InfNaN/0; - } + fromJSONValue(obj) { + return obj.$InfNaN / 0; + }, }, { // Binary - matchJSONValue: function (obj) { - return _.has(obj, '$binary') && _.size(obj) === 1; + matchJSONValue(obj) { + return hasOwn(obj, '$binary') && Object.keys(obj).length === 1; }, - matchObject: function (obj) { + matchObject(obj) { return typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array - || (obj && _.has(obj, '$Uint8ArrayPolyfill')); + || (obj && hasOwn(obj, '$Uint8ArrayPolyfill')); }, - toJSONValue: function (obj) { + toJSONValue(obj) { return {$binary: Base64.encode(obj)}; }, - fromJSONValue: function (obj) { + fromJSONValue(obj) { return Base64.decode(obj.$binary); - } + }, }, { // Escaping one level - matchJSONValue: function (obj) { - return _.has(obj, '$escape') && _.size(obj) === 1; + matchJSONValue(obj) { + return hasOwn(obj, '$escape') && Object.keys(obj).length === 1; }, - matchObject: function (obj) { - if (_.isEmpty(obj) || _.size(obj) > 2) { - return false; + matchObject(obj) { + let match = false; + if (obj) { + const keyCount = Object.keys(obj).length; + if (keyCount === 1 || keyCount === 2) { + match = + builtinConverters.some(converter => converter.matchJSONValue(obj)); + } } - return _.any(builtinConverters, function (converter) { - return converter.matchJSONValue(obj); - }); + return match; }, - toJSONValue: function (obj) { - var newObj = {}; - _.each(obj, function (value, key) { - newObj[key] = EJSON.toJSONValue(value); + toJSONValue(obj) { + const newObj = {}; + Object.keys(obj).forEach(key => { + newObj[key] = EJSON.toJSONValue(obj[key]); }); return {$escape: newObj}; }, - fromJSONValue: function (obj) { - var newObj = {}; - _.each(obj.$escape, function (value, key) { - newObj[key] = EJSON.fromJSONValue(value); + fromJSONValue(obj) { + const newObj = {}; + Object.keys(obj.$escape).forEach(key => { + newObj[key] = EJSON.fromJSONValue(obj.$escape[key]); }); return newObj; - } + }, }, { // Custom - matchJSONValue: function (obj) { - return _.has(obj, '$type') && _.has(obj, '$value') && _.size(obj) === 2; + matchJSONValue(obj) { + return hasOwn(obj, '$type') + && hasOwn(obj, '$value') && Object.keys(obj).length === 2; }, - matchObject: function (obj) { + matchObject(obj) { return EJSON._isCustomType(obj); }, - toJSONValue: function (obj) { - var jsonValue = Meteor._noYieldsAllowed(function () { - return obj.toJSONValue(); - }); + toJSONValue(obj) { + const jsonValue = Meteor._noYieldsAllowed(() => obj.toJSONValue()); return {$type: obj.typeName(), $value: jsonValue}; }, - fromJSONValue: function (obj) { - var typeName = obj.$type; - if (!_.has(customTypes, typeName)) - throw new Error("Custom EJSON type " + typeName + " is not defined"); - var converter = customTypes[typeName]; - return Meteor._noYieldsAllowed(function () { - return converter(obj.$value); - }); - } - } + fromJSONValue(obj) { + const typeName = obj.$type; + if (!hasOwn(customTypes, typeName)) { + throw new Error(`Custom EJSON type ${typeName} is not defined`); + } + const converter = customTypes[typeName]; + return Meteor._noYieldsAllowed(() => converter(obj.$value)); + }, + }, ]; -EJSON._isCustomType = function (obj) { - return obj && - typeof obj.toJSONValue === 'function' && - typeof obj.typeName === 'function' && - _.has(customTypes, obj.typeName()); -}; +EJSON._isCustomType = (obj) => ( + obj && + typeof obj.toJSONValue === 'function' && + typeof obj.typeName === 'function' && + hasOwn(customTypes, obj.typeName()) +); -EJSON._getTypes = function () { - return customTypes; -}; +EJSON._getTypes = () => customTypes; -EJSON._getConverters = function () { - return builtinConverters; +EJSON._getConverters = () => builtinConverters; + +// Either return the JSON-compatible version of the argument, or undefined (if +// the item isn't itself replaceable, but maybe some fields in it are) +const toJSONValueHelper = item => { + for (let i = 0; i < builtinConverters.length; i++) { + const converter = builtinConverters[i]; + if (converter.matchObject(item)) { + return converter.toJSONValue(item); + } + } + return undefined; }; // for both arrays and objects, in-place modification. -var adjustTypesToJSONValue = -EJSON._adjustTypesToJSONValue = function (obj) { +const adjustTypesToJSONValue = obj => { // Is it an atom that we need to adjust? - if (obj === null) + if (obj === null) { return null; - var maybeChanged = toJSONValueHelper(obj); - if (maybeChanged !== undefined) + } + + const maybeChanged = toJSONValueHelper(obj); + if (maybeChanged !== undefined) { return maybeChanged; + } // Other atoms are unchanged. - if (typeof obj !== 'object') + if (typeof obj !== 'object') { return obj; + } // Iterate over array or object structure. - _.each(obj, function (value, key) { + Object.keys(obj).forEach(key => { + const value = obj[key]; if (typeof value !== 'object' && value !== undefined && - !isInfOrNan(value)) + !isInfOrNan(value)) { return; // continue + } - var changed = toJSONValueHelper(value); + const changed = toJSONValueHelper(value); if (changed) { obj[key] = changed; return; // on to the next key @@ -246,53 +271,70 @@ EJSON._adjustTypesToJSONValue = function (obj) { return obj; }; -// Either return the JSON-compatible version of the argument, or undefined (if -// the item isn't itself replaceable, but maybe some fields in it are) -var toJSONValueHelper = function (item) { - for (var i = 0; i < builtinConverters.length; i++) { - var converter = builtinConverters[i]; - if (converter.matchObject(item)) { - return converter.toJSONValue(item); - } - } - return undefined; -}; +EJSON._adjustTypesToJSONValue = adjustTypesToJSONValue; /** - * @summary Serialize an EJSON-compatible value into its plain JSON representation. + * @summary Serialize an EJSON-compatible value into its plain JSON + * representation. * @locus Anywhere * @param {EJSON} val A value to serialize to plain JSON. */ -EJSON.toJSONValue = function (item) { - var changed = toJSONValueHelper(item); - if (changed !== undefined) +EJSON.toJSONValue = item => { + const changed = toJSONValueHelper(item); + if (changed !== undefined) { return changed; - if (typeof item === 'object') { - item = EJSON.clone(item); - adjustTypesToJSONValue(item); } - return item; + + let newItem = item; + if (typeof item === 'object') { + newItem = EJSON.clone(item); + adjustTypesToJSONValue(newItem); + } + return newItem; +}; + +// Either return the argument changed to have the non-json +// rep of itself (the Object version) or the argument itself. +// DOES NOT RECURSE. For actually getting the fully-changed value, use +// EJSON.fromJSONValue +const fromJSONValueHelper = value => { + if (typeof value === 'object' && value !== null) { + const keys = Object.keys(value); + if (keys.length <= 2 + && keys.every(k => typeof k === 'string' && k.substr(0, 1) === '$')) { + for (let i = 0; i < builtinConverters.length; i++) { + const converter = builtinConverters[i]; + if (converter.matchJSONValue(value)) { + return converter.fromJSONValue(value); + } + } + } + } + return value; }; // for both arrays and objects. Tries its best to just // use the object you hand it, but may return something // different if the object you hand it itself needs changing. -// -var adjustTypesFromJSONValue = -EJSON._adjustTypesFromJSONValue = function (obj) { - if (obj === null) +const adjustTypesFromJSONValue = obj => { + if (obj === null) { return null; - var maybeChanged = fromJSONValueHelper(obj); - if (maybeChanged !== obj) + } + + const maybeChanged = fromJSONValueHelper(obj); + if (maybeChanged !== obj) { return maybeChanged; + } // Other atoms are unchanged. - if (typeof obj !== 'object') + if (typeof obj !== 'object') { return obj; + } - _.each(obj, function (value, key) { + Object.keys(obj).forEach(key => { + const value = obj[key]; if (typeof value === 'object') { - var changed = fromJSONValueHelper(value); + const changed = fromJSONValueHelper(value); if (value !== changed) { obj[key] = changed; return; @@ -305,171 +347,186 @@ EJSON._adjustTypesFromJSONValue = function (obj) { return obj; }; -// Either return the argument changed to have the non-json -// rep of itself (the Object version) or the argument itself. - -// DOES NOT RECURSE. For actually getting the fully-changed value, use -// EJSON.fromJSONValue -var fromJSONValueHelper = function (value) { - if (typeof value === 'object' && value !== null) { - if (_.size(value) <= 2 - && _.all(value, function (v, k) { - return typeof k === 'string' && k.substr(0, 1) === '$'; - })) { - for (var i = 0; i < builtinConverters.length; i++) { - var converter = builtinConverters[i]; - if (converter.matchJSONValue(value)) { - return converter.fromJSONValue(value); - } - } - } - } - return value; -}; +EJSON._adjustTypesFromJSONValue = adjustTypesFromJSONValue; /** * @summary Deserialize an EJSON value from its plain JSON representation. * @locus Anywhere * @param {JSONCompatible} val A value to deserialize into EJSON. */ -EJSON.fromJSONValue = function (item) { - var changed = fromJSONValueHelper(item); +EJSON.fromJSONValue = item => { + let changed = fromJSONValueHelper(item); if (changed === item && typeof item === 'object') { - item = EJSON.clone(item); - adjustTypesFromJSONValue(item); - return item; - } else { - return changed; + changed = EJSON.clone(item); + adjustTypesFromJSONValue(changed); } + return changed; }; /** - * @summary Serialize a value to a string. - -For EJSON values, the serialization fully represents the value. For non-EJSON values, serializes the same way as `JSON.stringify`. + * @summary Serialize a value to a string. For EJSON values, the serialization + * fully represents the value. For non-EJSON values, serializes the + * same way as `JSON.stringify`. * @locus Anywhere * @param {EJSON} val A value to stringify. * @param {Object} [options] - * @param {Boolean | Integer | String} options.indent Indents objects and arrays for easy readability. When `true`, indents by 2 spaces; when an integer, indents by that number of spaces; and when a string, uses the string as the indentation pattern. - * @param {Boolean} options.canonical When `true`, stringifies keys in an object in sorted order. + * @param {Boolean | Integer | String} options.indent Indents objects and + * arrays for easy readability. When `true`, indents by 2 spaces; when an + * integer, indents by that number of spaces; and when a string, uses the + * string as the indentation pattern. + * @param {Boolean} options.canonical When `true`, stringifies keys in an + * object in sorted order. */ -EJSON.stringify = function (item, options) { - var json = EJSON.toJSONValue(item); +EJSON.stringify = (item, options) => { + let serialized; + const json = EJSON.toJSONValue(item); if (options && (options.canonical || options.indent)) { - return EJSON._canonicalStringify(json, options); + import canonicalStringify from './stringify'; + serialized = canonicalStringify(json, options); } else { - return JSON.stringify(json); + serialized = JSON.stringify(json); } + return serialized; }; /** - * @summary Parse a string into an EJSON value. Throws an error if the string is not valid EJSON. + * @summary Parse a string into an EJSON value. Throws an error if the string + * is not valid EJSON. * @locus Anywhere * @param {String} str A string to parse into an EJSON value. */ -EJSON.parse = function (item) { - if (typeof item !== 'string') - throw new Error("EJSON.parse argument should be a string"); +EJSON.parse = item => { + if (typeof item !== 'string') { + throw new Error('EJSON.parse argument should be a string'); + } return EJSON.fromJSONValue(JSON.parse(item)); }; /** - * @summary Returns true if `x` is a buffer of binary data, as returned from [`EJSON.newBinary`](#ejson_new_binary). + * @summary Returns true if `x` is a buffer of binary data, as returned from + * [`EJSON.newBinary`](#ejson_new_binary). * @param {Object} x The variable to check. * @locus Anywhere */ -EJSON.isBinary = function (obj) { +EJSON.isBinary = obj => { return !!((typeof Uint8Array !== 'undefined' && obj instanceof Uint8Array) || (obj && obj.$Uint8ArrayPolyfill)); }; /** - * @summary Return true if `a` and `b` are equal to each other. Return false otherwise. Uses the `equals` method on `a` if present, otherwise performs a deep comparison. + * @summary Return true if `a` and `b` are equal to each other. Return false + * otherwise. Uses the `equals` method on `a` if present, otherwise + * performs a deep comparison. * @locus Anywhere * @param {EJSON} a * @param {EJSON} b * @param {Object} [options] - * @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The default is `false`. + * @param {Boolean} options.keyOrderSensitive Compare in key sensitive order, + * if supported by the JavaScript implementation. For example, `{a: 1, b: 2}` + * is equal to `{b: 2, a: 1}` only when `keyOrderSensitive` is `false`. The + * default is `false`. */ -EJSON.equals = function (a, b, options) { - var i; - var keyOrderSensitive = !!(options && options.keyOrderSensitive); - if (a === b) +EJSON.equals = (a, b, options) => { + let i; + const keyOrderSensitive = !!(options && options.keyOrderSensitive); + if (a === b) { return true; - if (_.isNaN(a) && _.isNaN(b)) - return true; // This differs from the IEEE spec for NaN equality, b/c we don't want - // anything ever with a NaN to be poisoned from becoming equal to anything. - if (!a || !b) // if either one is falsy, they'd have to be === to be equal + } + + // This differs from the IEEE spec for NaN equality, b/c we don't want + // anything ever with a NaN to be poisoned from becoming equal to anything. + if (Number.isNaN(a) && Number.isNaN(b)) { + return true; + } + + // if either one is falsy, they'd have to be === to be equal + if (!a || !b) { return false; - if (!(typeof a === 'object' && typeof b === 'object')) + } + + if (!(typeof a === 'object' && typeof b === 'object')) { return false; - if (a instanceof Date && b instanceof Date) + } + + if (a instanceof Date && b instanceof Date) { return a.valueOf() === b.valueOf(); + } + if (EJSON.isBinary(a) && EJSON.isBinary(b)) { - if (a.length !== b.length) + if (a.length !== b.length) { return false; + } for (i = 0; i < a.length; i++) { - if (a[i] !== b[i]) + if (a[i] !== b[i]) { return false; + } } return true; } - if (typeof (a.equals) === 'function') + + if (typeof (a.equals) === 'function') { return a.equals(b, options); - if (typeof (b.equals) === 'function') + } + + if (typeof (b.equals) === 'function') { return b.equals(a, options); + } + if (a instanceof Array) { - if (!(b instanceof Array)) + if (!(b instanceof Array)) { return false; - if (a.length !== b.length) + } + if (a.length !== b.length) { return false; + } for (i = 0; i < a.length; i++) { - if (!EJSON.equals(a[i], b[i], options)) + if (!EJSON.equals(a[i], b[i], options)) { return false; + } } return true; } + // fallback for custom types that don't implement their own equals switch (EJSON._isCustomType(a) + EJSON._isCustomType(b)) { case 1: return false; case 2: return EJSON.equals(EJSON.toJSONValue(a), EJSON.toJSONValue(b)); + default: // Do nothing } + // fall back to structural equality of objects - var ret; + let ret; + const aKeys = Object.keys(a); + const bKeys = Object.keys(b); if (keyOrderSensitive) { - var bKeys = []; - _.each(b, function (val, x) { - bKeys.push(x); - }); i = 0; - ret = _.all(a, function (val, x) { + ret = aKeys.every(key => { if (i >= bKeys.length) { return false; } - if (x !== bKeys[i]) { + if (key !== bKeys[i]) { return false; } - if (!EJSON.equals(val, b[bKeys[i]], options)) { + if (!EJSON.equals(a[key], b[bKeys[i]], options)) { return false; } i++; return true; }); - return ret && i === bKeys.length; } else { i = 0; - ret = _.all(a, function (val, key) { - if (!_.has(b, key)) { + ret = aKeys.every(key => { + if (!hasOwn(b, key)) { return false; } - if (!EJSON.equals(val, b[key], options)) { + if (!EJSON.equals(a[key], b[key], options)) { return false; } i++; return true; }); - return ret && _.keys(b).length === i; } + return ret && i === bKeys.length; }; /** @@ -477,49 +534,57 @@ EJSON.equals = function (a, b, options) { * @locus Anywhere * @param {EJSON} val A value to copy. */ -EJSON.clone = function (v) { - var ret; - if (typeof v !== "object") +EJSON.clone = v => { + let ret; + if (typeof v !== 'object') { return v; - if (v === null) + } + + if (v === null) { return null; // null has typeof "object" - if (v instanceof Date) + } + + if (v instanceof Date) { return new Date(v.getTime()); + } + // RegExps are not really EJSON elements (eg we don't define a serialization // for them), but they're immutable anyway, so we can support them in clone. - if (v instanceof RegExp) + if (v instanceof RegExp) { return v; + } + if (EJSON.isBinary(v)) { ret = EJSON.newBinary(v.length); - for (var i = 0; i < v.length; i++) { + for (let i = 0; i < v.length; i++) { ret[i] = v[i]; } return ret; } - // XXX: Use something better than underscore's isArray - if (_.isArray(v) || _.isArguments(v)) { - // For some reason, _.map doesn't work in this context on Opera (weird test - // failures). - ret = []; - for (i = 0; i < v.length; i++) - ret[i] = EJSON.clone(v[i]); - return ret; + + if (Array.isArray(v)) { + return v.map(value => EJSON.clone(value)); } + + if (isArguments(v)) { + return Array.from(v).map(value => EJSON.clone(value)); + } + // handle general user-defined typed Objects if they have a clone method if (typeof v.clone === 'function') { return v.clone(); } + // handle other custom types if (EJSON._isCustomType(v)) { return EJSON.fromJSONValue(EJSON.clone(EJSON.toJSONValue(v)), true); } + // handle other objects ret = {}; - var keys = _.keys(v); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; + Object.keys(v).forEach((key) => { ret[key] = EJSON.clone(v[key]); - } + }); return ret; }; @@ -534,3 +599,5 @@ EJSON.clone = function (v) { // then 'base64' would have to use EJSON.newBinary, and 'ejson' would // also have to use 'base64'.) EJSON.newBinary = Base64.newBinary; + +export { EJSON }; diff --git a/packages/ejson/ejson_tests.js b/packages/ejson/ejson_tests.js index f4d1952f86..c6c01751af 100644 --- a/packages/ejson/ejson_tests.js +++ b/packages/ejson/ejson_tests.js @@ -1,64 +1,67 @@ -Tinytest.add("ejson - keyOrderSensitive", function (test) { +import { EJSON } from './ejson'; +import EJSONTest from './custom_models_for_tests'; + +Tinytest.add('ejson - keyOrderSensitive', test => { test.isTrue(EJSON.equals({ a: {b: 1, c: 2}, - d: {e: 3, f: 4} + d: {e: 3, f: 4}, }, { d: {f: 4, e: 3}, - a: {c: 2, b: 1} + a: {c: 2, b: 1}, })); test.isFalse(EJSON.equals({ a: {b: 1, c: 2}, - d: {e: 3, f: 4} + d: {e: 3, f: 4}, }, { d: {f: 4, e: 3}, - a: {c: 2, b: 1} + a: {c: 2, b: 1}, }, {keyOrderSensitive: true})); test.isFalse(EJSON.equals({ a: {b: 1, c: 2}, - d: {e: 3, f: 4} + d: {e: 3, f: 4}, }, { a: {c: 2, b: 1}, - d: {f: 4, e: 3} + d: {f: 4, e: 3}, }, {keyOrderSensitive: true})); - test.isFalse(EJSON.equals({a: {}}, {a: {b:2}}, {keyOrderSensitive: true})); - test.isFalse(EJSON.equals({a: {b:2}}, {a: {}}, {keyOrderSensitive: true})); + test.isFalse(EJSON.equals({a: {}}, {a: {b: 2}}, {keyOrderSensitive: true})); + test.isFalse(EJSON.equals({a: {b: 2}}, {a: {}}, {keyOrderSensitive: true})); }); -Tinytest.add("ejson - nesting and literal", function (test) { - var d = new Date; - var obj = {$date: d}; - var eObj = EJSON.toJSONValue(obj); - var roundTrip = EJSON.fromJSONValue(eObj); +Tinytest.add('ejson - nesting and literal', test => { + const d = new Date(); + const obj = {$date: d}; + const eObj = EJSON.toJSONValue(obj); + const roundTrip = EJSON.fromJSONValue(eObj); test.equal(obj, roundTrip); }); -Tinytest.add("ejson - some equality tests", function (test) { +Tinytest.add('ejson - some equality tests', test => { test.isTrue(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, c: 3, b: 2})); test.isFalse(EJSON.equals({a: 1, b: 2}, {a: 1, c: 3, b: 2})); test.isFalse(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, b: 2})); test.isFalse(EJSON.equals({a: 1, b: 2, c: 3}, {a: 1, c: 3, b: 4})); - test.isFalse(EJSON.equals({a: {}}, {a: {b:2}})); - test.isFalse(EJSON.equals({a: {b:2}}, {a: {}})); + test.isFalse(EJSON.equals({a: {}}, {a: {b: 2}})); + test.isFalse(EJSON.equals({a: {b: 2}}, {a: {}})); }); -Tinytest.add("ejson - equality and falsiness", function (test) { +Tinytest.add('ejson - equality and falsiness', test => { test.isTrue(EJSON.equals(null, null)); test.isTrue(EJSON.equals(undefined, undefined)); - test.isFalse(EJSON.equals({foo: "foo"}, null)); - test.isFalse(EJSON.equals(null, {foo: "foo"})); - test.isFalse(EJSON.equals(undefined, {foo: "foo"})); - test.isFalse(EJSON.equals({foo: "foo"}, undefined)); + test.isFalse(EJSON.equals({foo: 'foo'}, null)); + test.isFalse(EJSON.equals(null, {foo: 'foo'})); + test.isFalse(EJSON.equals(undefined, {foo: 'foo'})); + test.isFalse(EJSON.equals({foo: 'foo'}, undefined)); }); -Tinytest.add("ejson - NaN and Inf", function (test) { - test.equal(EJSON.parse("{\"$InfNaN\": 1}"), Infinity); - test.equal(EJSON.parse("{\"$InfNaN\": -1}"), -Infinity); - test.isTrue(_.isNaN(EJSON.parse("{\"$InfNaN\": 0}"))); +Tinytest.add('ejson - NaN and Inf', test => { + test.equal(EJSON.parse('{"$InfNaN": 1}'), Infinity); + test.equal(EJSON.parse('{"$InfNaN": -1}'), -Infinity); + test.isTrue(Number.isNaN(EJSON.parse('{"$InfNaN": 0}'))); test.equal(EJSON.parse(EJSON.stringify(Infinity)), Infinity); test.equal(EJSON.parse(EJSON.stringify(-Infinity)), -Infinity); - test.isTrue(_.isNaN(EJSON.parse(EJSON.stringify(NaN)))); + test.isTrue(Number.isNaN(EJSON.parse(EJSON.stringify(NaN)))); test.isTrue(EJSON.equals(NaN, NaN)); test.isTrue(EJSON.equals(Infinity, Infinity)); test.isTrue(EJSON.equals(-Infinity, -Infinity)); @@ -68,61 +71,61 @@ Tinytest.add("ejson - NaN and Inf", function (test) { test.isFalse(EJSON.equals(NaN, 0)); test.isTrue(EJSON.equals( - EJSON.parse("{\"a\": {\"$InfNaN\": 1}}"), + EJSON.parse('{"a": {"$InfNaN": 1}}'), {a: Infinity} )); test.isTrue(EJSON.equals( - EJSON.parse("{\"a\": {\"$InfNaN\": 0}}"), + EJSON.parse('{"a": {"$InfNaN": 0}}'), {a: NaN} )); }); -Tinytest.add("ejson - clone", function (test) { - var cloneTest = function (x, identical) { - var y = EJSON.clone(x); +Tinytest.add('ejson - clone', test => { + const cloneTest = (x, identical) => { + const y = EJSON.clone(x); test.isTrue(EJSON.equals(x, y)); test.equal(x === y, !!identical); }; cloneTest(null, true); cloneTest(undefined, true); cloneTest(42, true); - cloneTest("asdf", true); + cloneTest('asdf', true); cloneTest([1, 2, 3]); - cloneTest([1, "fasdf", {foo: 42}]); - cloneTest({x: 42, y: "asdf"}); + cloneTest([1, 'fasdf', {foo: 42}]); + cloneTest({x: 42, y: 'asdf'}); - var testCloneArgs = function (/*arguments*/) { - var clonedArgs = EJSON.clone(arguments); - test.equal(clonedArgs, [1, 2, "foo", [4]]); + function testCloneArgs(/*arguments*/) { + const clonedArgs = EJSON.clone(arguments); + test.equal(clonedArgs, [1, 2, 'foo', [4]]); }; - testCloneArgs(1, 2, "foo", [4]); + testCloneArgs(1, 2, 'foo', [4]); }); -Tinytest.add("ejson - stringify", function (test) { - test.equal(EJSON.stringify(null), "null"); - test.equal(EJSON.stringify(true), "true"); - test.equal(EJSON.stringify(false), "false"); - test.equal(EJSON.stringify(123), "123"); - test.equal(EJSON.stringify("abc"), "\"abc\""); +Tinytest.add('ejson - stringify', test => { + test.equal(EJSON.stringify(null), 'null'); + test.equal(EJSON.stringify(true), 'true'); + test.equal(EJSON.stringify(false), 'false'); + test.equal(EJSON.stringify(123), '123'); + test.equal(EJSON.stringify('abc'), '"abc"'); test.equal(EJSON.stringify([1, 2, 3]), - "[1,2,3]" + '[1,2,3]' ); test.equal(EJSON.stringify([1, 2, 3], {indent: true}), - "[\n 1,\n 2,\n 3\n]" + '[\n 1,\n 2,\n 3\n]' ); test.equal(EJSON.stringify([1, 2, 3], {canonical: false}), - "[1,2,3]" + '[1,2,3]' ); test.equal(EJSON.stringify([1, 2, 3], {indent: true, canonical: false}), - "[\n 1,\n 2,\n 3\n]" + '[\n 1,\n 2,\n 3\n]' ); test.equal(EJSON.stringify([1, 2, 3], {indent: 4}), - "[\n 1,\n 2,\n 3\n]" + '[\n 1,\n 2,\n 3\n]' ); test.equal(EJSON.stringify([1, 2, 3], {indent: '--'}), - "[\n--1,\n--2,\n--3\n]" + '[\n--1,\n--2,\n--3\n]' ); test.equal( @@ -130,62 +133,61 @@ Tinytest.add("ejson - stringify", function (test) { {b: [2, {d: 4, c: 3}], a: 1}, {canonical: true} ), - "{\"a\":1,\"b\":[2,{\"c\":3,\"d\":4}]}" + '{"a":1,"b":[2,{"c":3,"d":4}]}' ); test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, { indent: true, - canonical: true + canonical: true, } ), - "{\n" + - " \"a\": 1,\n" + - " \"b\": [\n" + - " 2,\n" + - " {\n" + - " \"c\": 3,\n" + - " \"d\": 4\n" + - " }\n" + - " ]\n" + - "}" + '{\n' + + ' "a": 1,\n' + + ' "b": [\n' + + ' 2,\n' + + ' {\n' + + ' "c": 3,\n' + + ' "d": 4\n' + + ' }\n' + + ' ]\n' + + '}' ); test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, {canonical: false} ), - "{\"b\":[2,{\"d\":4,\"c\":3}],\"a\":1}" + '{"b":[2,{"d":4,"c":3}],"a":1}' ); test.equal( EJSON.stringify( {b: [2, {d: 4, c: 3}], a: 1}, {indent: true, canonical: false} ), - "{\n" + - " \"b\": [\n" + - " 2,\n" + - " {\n" + - " \"d\": 4,\n" + - " \"c\": 3\n" + - " }\n" + - " ],\n" + - " \"a\": 1\n" + - "}" - + '{\n' + + ' "b": [\n' + + ' 2,\n' + + ' {\n' + + ' "d": 4,\n' + + ' "c": 3\n' + + ' }\n' + + ' ],\n' + + ' "a": 1\n' + + '}' ); }); -Tinytest.add("ejson - parse", function (test) { - test.equal(EJSON.parse("[1,2,3]"), [1,2,3]); +Tinytest.add('ejson - parse', test => { + test.equal(EJSON.parse('[1,2,3]'), [1, 2, 3]); test.throws( - function () { EJSON.parse(null) }, + () => { EJSON.parse(null); }, /argument should be a string/ ); }); -Tinytest.add("ejson - regexp", function (test) { +Tinytest.add("ejson - regexp", test => { test.equal(EJSON.stringify(/foo/gi), "{\"$regexp\":\"foo\",\"$flags\":\"gi\"}"); var d = new RegExp("foo", "gi"); var obj = { $regexp: "foo", $flags: "gi" }; @@ -195,80 +197,82 @@ Tinytest.add("ejson - regexp", function (test) { test.equal(obj, roundTrip); }); -Tinytest.add("ejson - custom types", function (test) { - var testSameConstructors = function (obj, compareWith) { - test.equal(obj.constructor, compareWith.constructor); - if (typeof obj === 'object') { - _.each(obj, function(value, key) { +Tinytest.add('ejson - custom types', test => { + const testSameConstructors = (someObj, compareWith) => { + test.equal(someObj.constructor, compareWith.constructor); + if (typeof someObj === 'object') { + Object.keys(someObj).forEach(key => { + const value = someObj[key]; testSameConstructors(value, compareWith[key]); }); } - } - var testReallyEqual = function (obj, compareWith) { - test.equal(obj, compareWith); - testSameConstructors(obj, compareWith); - } - var testRoundTrip = function (obj) { - var str = EJSON.stringify(obj); - var roundTrip = EJSON.parse(str); - testReallyEqual(obj, roundTrip); - } - var testCustomObject = function (obj) { - testRoundTrip(obj); - testReallyEqual(obj, EJSON.clone(obj)); - } + }; - var a = new EJSONTest.Address('Montreal', 'Quebec'); + const testReallyEqual = (someObj, compareWith) => { + test.equal(someObj, compareWith); + testSameConstructors(someObj, compareWith); + }; + + const testRoundTrip = (someObj) => { + const str = EJSON.stringify(someObj); + const roundTrip = EJSON.parse(str); + testReallyEqual(someObj, roundTrip); + }; + + const testCustomObject = (someObj) => { + testRoundTrip(someObj); + testReallyEqual(someObj, EJSON.clone(someObj)); + }; + + const a = new EJSONTest.Address('Montreal', 'Quebec'); testCustomObject( {address: a} ); // Test that difference is detected even if they // have similar toJSONValue results: - var nakedA = {city: 'Montreal', state: 'Quebec'}; + const nakedA = {city: 'Montreal', state: 'Quebec'}; test.notEqual(nakedA, a); test.notEqual(a, nakedA); - var holder = new EJSONTest.Holder(nakedA); + const holder = new EJSONTest.Holder(nakedA); test.equal(holder.toJSONValue(), a.toJSONValue()); // sanity check test.notEqual(holder, a); test.notEqual(a, holder); - - var d = new Date; - var obj = new EJSONTest.Person("John Doe", d, a); + const d = new Date(); + const obj = new EJSONTest.Person('John Doe', d, a); testCustomObject( obj ); // Test clone is deep: - var clone = EJSON.clone(obj); + const clone = EJSON.clone(obj); clone.address.city = 'Sherbrooke'; test.notEqual( obj, clone ); }); // Verify objects with a property named "length" can be handled by the EJSON // API properly (see https://github.com/meteor/meteor/issues/5175). -Tinytest.add( - "ejson - handle objects with properties named 'length'", - function (test) { - var Widget = function () { +Tinytest.add('ejson - handle objects with properties named "length"', test => { + class Widget { + constructor() { this.length = 10; - }; - var widget = new Widget(); - - var toJsonWidget = EJSON.toJSONValue(widget); - test.equal(widget, toJsonWidget); - - var fromJsonWidget = EJSON.fromJSONValue(widget); - test.equal(widget, fromJsonWidget); - - var stringifiedWidget = EJSON.stringify(widget); - test.equal(stringifiedWidget, '{"length":10}'); - - var parsedWidget = EJSON.parse('{"length":10}'); - test.equal({ length: 10 }, parsedWidget); - - test.isFalse(EJSON.isBinary(widget)); - - var widget2 = new Widget(); - test.isTrue(widget, widget2); - - var clonedWidget = EJSON.clone(widget); - test.equal(widget, clonedWidget); + } } -); + const widget = new Widget(); + + const toJsonWidget = EJSON.toJSONValue(widget); + test.equal(widget, toJsonWidget); + + const fromJsonWidget = EJSON.fromJSONValue(widget); + test.equal(widget, fromJsonWidget); + + const stringifiedWidget = EJSON.stringify(widget); + test.equal(stringifiedWidget, '{"length":10}'); + + const parsedWidget = EJSON.parse('{"length":10}'); + test.equal({ length: 10 }, parsedWidget); + + test.isFalse(EJSON.isBinary(widget)); + + const widget2 = new Widget(); + test.isTrue(widget, widget2); + + const clonedWidget = EJSON.clone(widget); + test.equal(widget, clonedWidget); +}); diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 5fa9cb1e24..0259ea994e 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -1,19 +1,16 @@ Package.describe({ - summary: "Extended and Extensible JSON library", - version: '1.0.14' + summary: 'Extended and Extensible JSON library', + version: '1.0.14', }); -Package.onUse(function (api) { - api.use(['underscore', 'base64']); +Package.onUse(function onUse(api) { + api.use(['ecmascript', 'base64']); api.mainModule('ejson.js'); - api.addFiles('stringify.js'); api.export('EJSON'); - api.export('EJSONTest', { testOnly: true }); }); -Package.onTest(function (api) { - api.use(['tinytest', 'underscore']); +Package.onTest(function onTest(api) { + api.use(['ecmascript', 'tinytest']); api.use('ejson'); - api.addFiles('custom_models_for_tests.js'); api.mainModule('ejson_tests.js'); }); diff --git a/packages/ejson/stringify.js b/packages/ejson/stringify.js index 1ce9ad6baf..d571ae6c48 100644 --- a/packages/ejson/stringify.js +++ b/packages/ejson/stringify.js @@ -11,20 +11,10 @@ function quote(string) { return JSON.stringify(string); } -var str = function (key, holder, singleIndent, outerIndent, canonical) { - - // Produce a string from holder[key]. - - var i; // The loop counter. - var k; // The member key. - var v; // The member value. - var length; - var innerIndent = outerIndent; - var partial; - var value = holder[key]; +const str = (key, holder, singleIndent, outerIndent, canonical) => { + const value = holder[key]; // What happens next depends on the value's type. - switch (typeof value) { case 'string': return quote(value); @@ -41,78 +31,91 @@ var str = function (key, holder, singleIndent, outerIndent, canonical) { if (!value) { return 'null'; } - // Make an array to hold the partial results of stringifying this object value. - innerIndent = outerIndent + singleIndent; - partial = []; + // Make an array to hold the partial results of stringifying this object + // value. + const innerIndent = outerIndent + singleIndent; + const partial = []; // Is the value an array? - if (_.isArray(value) || _.isArguments(value)) { - - // The value is an array. Stringify every element. Use null as a placeholder - // for non-JSON values. - - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value, singleIndent, innerIndent, canonical) || 'null'; + if (Array.isArray(value) || ({}).hasOwnProperty.call(value, 'callee')) { + // The value is an array. Stringify every element. Use null as a + // placeholder for non-JSON values. + const length = value.length; + for (let i = 0; i < length; i += 1) { + partial[i] = + str(i, value, singleIndent, innerIndent, canonical) || 'null'; } - // Join all of the elements together, separated with commas, and wrap them in - // brackets. - + // Join all of the elements together, separated with commas, and wrap + // them in brackets. + let v; if (partial.length === 0) { v = '[]'; } else if (innerIndent) { - v = '[\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + ']'; + v = '[\n' + + innerIndent + + partial.join(',\n' + + innerIndent) + + '\n' + + outerIndent + + ']'; } else { v = '[' + partial.join(',') + ']'; } return v; } - // Iterate through all of the keys in the object. - var keys = _.keys(value); - if (canonical) + let keys = Object.keys(value); + if (canonical) { keys = keys.sort(); - _.each(keys, function (k) { + } + keys.forEach(k => { v = str(k, value, singleIndent, innerIndent, canonical); if (v) { partial.push(quote(k) + (innerIndent ? ': ' : ':') + v); } }); - // Join all of the member texts together, separated with commas, // and wrap them in braces. - if (partial.length === 0) { v = '{}'; } else if (innerIndent) { - v = '{\n' + innerIndent + partial.join(',\n' + innerIndent) + '\n' + outerIndent + '}'; + v = '{\n' + + innerIndent + + partial.join(',\n' + + innerIndent) + + '\n' + + outerIndent + + '}'; } else { v = '{' + partial.join(',') + '}'; } return v; + + default: // Do nothing } -} +}; // If the JSON object does not yet have a stringify method, give it one. - -EJSON._canonicalStringify = function (value, options) { +const canonicalStringify = (value, options) => { // Make a fake root object containing our value under the key of ''. // Return the result of stringifying the value. - options = _.extend({ - indent: "", - canonical: false + const allOptions = Object.assign({ + indent: '', + canonical: false, }, options); - if (options.indent === true) { - options.indent = " "; - } else if (typeof options.indent === 'number') { - var newIndent = ""; - for (var i = 0; i < options.indent; i++) { + if (allOptions.indent === true) { + allOptions.indent = ' '; + } else if (typeof allOptions.indent === 'number') { + let newIndent = ''; + for (let i = 0; i < allOptions.indent; i++) { newIndent += ' '; } - options.indent = newIndent; + allOptions.indent = newIndent; } - return str('', {'': value}, options.indent, "", options.canonical); + return str('', {'': value}, allOptions.indent, '', allOptions.canonical); }; + +export default canonicalStringify; From a7aa0175f6b8d9de1926e3cd9cf1ca69f346bc45 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Sun, 16 Jul 2017 12:14:22 -0400 Subject: [PATCH 35/53] Swap 2 self-test's to get CircleCI tests to pass. --- tools/tests/package-tests.js | 90 ++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/tools/tests/package-tests.js b/tools/tests/package-tests.js index 7436d6a1b3..4985056957 100644 --- a/tools/tests/package-tests.js +++ b/tools/tests/package-tests.js @@ -183,6 +183,51 @@ selftest.define("change packages during hot code push", [], function () { run.stop(); }); +selftest.define("add debugOnly and prodOnly packages", [], function () { + var s = new Sandbox(); + var run; + + // Starting a run + s.createApp("myapp", "package-tests"); + s.cd("myapp"); + s.set("METEOR_OFFLINE_CATALOG", "t"); + + // Add a debugOnly package. It should work during a normal run, but print + // nothing in production mode. + run = s.run("add", "debug-only"); + run.match("debug-only"); + run.expectExit(0); + + s.mkdir("server"); + s.write("server/exit-test.js", + "process.exit(global.DEBUG_ONLY_LOADED ? 234 : 235)"); + + run = s.run("--once"); + run.waitSecs(15); + run.expectExit(234); + + run = s.run("--once", "--production"); + run.waitSecs(15); + run.expectExit(235); + + // Add prod-only package, which sets GLOBAL.PROD_ONLY_LOADED. + run = s.run("add", "prod-only"); + run.match("prod-only"); + run.expectExit(0); + + s.mkdir("server"); + s.write("server/exit-test.js", // overwrite + "process.exit(global.PROD_ONLY_LOADED ? 234 : 235)"); + + run = s.run("--once"); + run.waitSecs(15); + run.expectExit(235); + + run = s.run("--once", "--production"); + run.waitSecs(15); + run.expectExit(234); +}); + // Add packages through the command line. Make sure that the correct set of // changes is reflected in .meteor/packages, .meteor/versions and list. selftest.define("add packages to app", [], function () { @@ -274,51 +319,6 @@ selftest.define("add packages to app", [], function () { run.expectExit(0); }); -selftest.define("add debugOnly and prodOnly packages", [], function () { - var s = new Sandbox(); - var run; - - // Starting a run - s.createApp("myapp", "package-tests"); - s.cd("myapp"); - s.set("METEOR_OFFLINE_CATALOG", "t"); - - // Add a debugOnly package. It should work during a normal run, but print - // nothing in production mode. - run = s.run("add", "debug-only"); - run.match("debug-only"); - run.expectExit(0); - - s.mkdir("server"); - s.write("server/exit-test.js", - "process.exit(global.DEBUG_ONLY_LOADED ? 234 : 235)"); - - run = s.run("--once"); - run.waitSecs(15); - run.expectExit(234); - - run = s.run("--once", "--production"); - run.waitSecs(15); - run.expectExit(235); - - // Add prod-only package, which sets GLOBAL.PROD_ONLY_LOADED. - run = s.run("add", "prod-only"); - run.match("prod-only"); - run.expectExit(0); - - s.mkdir("server"); - s.write("server/exit-test.js", // overwrite - "process.exit(global.PROD_ONLY_LOADED ? 234 : 235)"); - - run = s.run("--once"); - run.waitSecs(15); - run.expectExit(235); - - run = s.run("--once", "--production"); - run.waitSecs(15); - run.expectExit(234); -}); - selftest.define("add package with both debugOnly and prodOnly", [], function () { var s = new Sandbox(); var run; From 5b39d4b2da3cda2137db7cc68b595ba27ca2c214 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Tue, 25 Jul 2017 21:32:42 -0400 Subject: [PATCH 36/53] Remove merged in underscore usage and update to ES5/ES6. --- packages/ejson/ejson.js | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/packages/ejson/ejson.js b/packages/ejson/ejson.js index b989e3b124..9102083739 100644 --- a/packages/ejson/ejson.js +++ b/packages/ejson/ejson.js @@ -105,22 +105,31 @@ const builtinConverters = [ }, }, { // RegExp - matchJSONValue: function (obj) { - return _.has(obj, '$regexp') && _.has(obj, '$flags') && _.size(obj) === 2; + matchJSONValue(obj) { + return hasOwn(obj, '$regexp') + && hasOwn(obj, '$flags') + && Object.keys(obj).length === 2; }, - matchObject: function (obj) { + matchObject(obj) { return obj instanceof RegExp; }, - toJSONValue: function (regexp) { - return { $regexp: regexp.source, $flags: regexp.flags }; + toJSONValue(regexp) { + return { + $regexp: regexp.source, + $flags: regexp.flags + }; + }, + fromJSONValue(obj) { + // Replaces duplicate / invalid flags. + return new RegExp( + obj.$regexp, + obj.$flags + // Cut off flags at 50 chars to avoid abusing RegExp for DOS. + .slice(0, 50) + .replace(/[^gimuy]/g,'') + .replace(/(.)(?=.*\1)/g, '') + ); }, - fromJSONValue: function (obj) { - // replaces duplicate / invalid flags - // cut of flags to 50 chars to avoid abusing regex for DOS - return new RegExp(obj.$regexp, obj.$flags.substr(0, 50) - .replace(/[^gimuy]/g,'') - .replace(/(.)(?=.*\1)/g, '')); - } }, { // NaN, Inf, -Inf. (These are the only objects with typeof !== 'object' // which we match.) From ebcaceb54bd3461eda7b1a0eb5a49d6a2ae0783b Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 15:48:04 -0400 Subject: [PATCH 37/53] Bump $BUNDLE_VERSION to 4.8.31 before rebuilding dev bundle. --- meteor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meteor b/meteor index dcbbf84a68..76e415db96 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.8.30 +BUNDLE_VERSION=4.8.31 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. From 565281e765cb9e5f5c10ac37c8ee03f42ecc1874 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 21:09:54 -0400 Subject: [PATCH 38/53] Revert "Add mongo-dev-server package (#8853)" This reverts commit 4d37a05fb33576b8f167168f4bc1b12821354615. After git bisecting between origin/release-1.5 and origin/release-1.5.2, I identified this commit as the culprit in recent failures of the modules test app: https://circleci.com/gh/meteor/meteor/4857#tests/containers/3 Note that the modules test app seems to be failing only on Linux, and it does pass reliably with this commit reverted. It must have something to do with Mongo failing to start, and thus the "App running at" message never appears, but I don't have a good theory why that might be. The command to run just the modules test app is meteor self-test --history 1000 'modules - test app' @zimme @hwillson @abernix any ideas? --- History.md | 12 ------------ packages/mongo-dev-server/README.md | 18 ------------------ packages/mongo-dev-server/package.js | 12 ------------ packages/mongo-dev-server/server.js | 3 --- packages/mongo/package.js | 3 +-- tools/runners/run-all.js | 19 +------------------ 6 files changed, 2 insertions(+), 65 deletions(-) delete mode 100644 packages/mongo-dev-server/README.md delete mode 100644 packages/mongo-dev-server/package.js delete mode 100644 packages/mongo-dev-server/server.js diff --git a/History.md b/History.md index 967f476a74..4b32985f86 100644 --- a/History.md +++ b/History.md @@ -30,18 +30,6 @@ root of the bundle, but it may be deprecated in a future version of Meteor. [PR #8956](https://github.com/meteor/meteor/pull/8956) -* A new package called `mongo-dev-server` has been created and wired into - `mongo` as a dependency. As long as this package is included in a Meteor - application (which it is by default since all new Meteor apps have `mongo` - as a dependency), a local development MongoDB server is started alongside - the application. This package was created to provide a way to disable the - local development Mongo server, when `mongo` isn't needed (e.g. when using - Meteor as a build system only). If an application has no dependency on - `mongo`, the `mongo-dev-server` package is not added, which means no local - development Mongo server is started. - [Feature Request #31](https://github.com/meteor/meteor-feature-requests/issues/31) - [PR #8853](https://github.com/meteor/meteor/pull/8853) - * `Accounts.config` no longer mistakenly allows tokens to expire when the `loginExpirationInDays` option is set to `null`. [Issue #5121](https://github.com/meteor/meteor/issues/5121) diff --git a/packages/mongo-dev-server/README.md b/packages/mongo-dev-server/README.md deleted file mode 100644 index 44609c29b5..0000000000 --- a/packages/mongo-dev-server/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# mongo-dev-server - -[Source code of released version](https://github.com/meteor/meteor/tree/master/packages/mongo-dev-server) | [Source code of development version](https://github.com/meteor/meteor/tree/devel/packages/mongo-dev-server) -*** - -When the `mongo-dev-server` package is included in a Meteor application, a -local development MongoDB server is started alongside the application. This -package is mostly used internally, as it is included by default with any -application that has a dependency on `mongo` (which is most Meteor -applications). In some cases however, people might be interested in -using the Meteor Tool without having to start a local development Mongo -instance (e.g. when using Meteor as a build system). If an application has no -dependency on `mongo`, the `mongo-dev-server` package will be removed -(since it is a direct dependency of the `mongo` package), and no local -development Mongo server will be started. - -Note this is a `debugOnly` package, meaning it will not be included in any -production bundles. diff --git a/packages/mongo-dev-server/package.js b/packages/mongo-dev-server/package.js deleted file mode 100644 index bf75976d23..0000000000 --- a/packages/mongo-dev-server/package.js +++ /dev/null @@ -1,12 +0,0 @@ -Package.describe({ - debugOnly: true, - documentation: 'README.md', - name: 'mongo-dev-server', - summary: 'Start MongoDB alongside Meteor, in development mode.', - version: '1.0.1-beta152.7', -}); - -Package.onUse(function (api) { - api.use('modules'); - api.mainModule('server.js', 'server'); -}); diff --git a/packages/mongo-dev-server/server.js b/packages/mongo-dev-server/server.js deleted file mode 100644 index 74643cab71..0000000000 --- a/packages/mongo-dev-server/server.js +++ /dev/null @@ -1,3 +0,0 @@ -if (process.env.MONGO_URL === 'no-mongo-server') { - Meteor._debug('Note: Restart Meteor to start the MongoDB server.'); -} diff --git a/packages/mongo/package.js b/packages/mongo/package.js index abd6da9650..1c9c4689be 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -34,8 +34,7 @@ Package.onUse(function (api) { 'diff-sequence', 'mongo-id', 'check', - 'ecmascript', - 'mongo-dev-server', + 'ecmascript' ]); // Binary Heap data structure is used to optimize oplog observe driver diff --git a/tools/runners/run-all.js b/tools/runners/run-all.js index 2da15c02a6..da225fe8c6 100644 --- a/tools/runners/run-all.js +++ b/tools/runners/run-all.js @@ -71,20 +71,10 @@ class Runner { onFailure }); - buildmessage.capture(function () { - self.projectContext.resolveConstraints(); - }); - - const packageMap = self.projectContext.packageMap; - const hasMongoDevServerPackage = - packageMap && packageMap.getInfo('mongo-dev-server') != null; self.mongoRunner = null; if (mongoUrl) { oplogUrl = disableOplog ? null : oplogUrl; - } else if (hasMongoDevServerPackage - || process.env.METEOR_TEST_FAKE_MONGOD_CONTROL_PORT) { - // The mongo-dev-server package is required to start Mongo, but - // tests using fake-mongod are exempted. + } else { self.mongoRunner = new MongoRunner({ projectLocalDir: self.projectContext.projectLocalDir, port: mongoPort, @@ -96,13 +86,6 @@ class Runner { mongoUrl = self.mongoRunner.mongoUrl(); oplogUrl = disableOplog ? null : self.mongoRunner.oplogUrl(); - } else { - // Don't start a mongodb server. - // Set monogUrl to a specific value to prevent MongoDB connections - // and to allow a check for printing a message if `mongo-dev-server` - // is added while the app is running. - // The check and message is printed by the `mongo-dev-server` package. - mongoUrl = 'no-mongo-server'; } self.updater = new Updater(); From 318232da9d2ca25380dad22aaf0863ca6170e7aa Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 3 Aug 2017 09:16:04 -0400 Subject: [PATCH 39/53] Try disabling longjohn completely, as it may affect test performance. --- tools/tool-testing/selftest.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/tools/tool-testing/selftest.js b/tools/tool-testing/selftest.js index fb8c4c69c7..c3809bd426 100644 --- a/tools/tool-testing/selftest.js +++ b/tools/tool-testing/selftest.js @@ -49,9 +49,6 @@ function checkTestOnlyDependency(name) { var phantomjs = checkTestOnlyDependency("phantomjs-prebuilt"); var webdriver = checkTestOnlyDependency('browserstack-webdriver'); -// To allow long stack traces that cross async boundaries -require('longjohn'); - // Exception representing a test failure var TestFailure = function (reason, details) { var self = this; From ada135f1244dcbec5539b2325065f7e6bd50dba6 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 3 Aug 2017 10:46:59 -0400 Subject: [PATCH 40/53] Try not deleting temp directories after Circle CI tests. Yet another hopeful attempt to stamp out segmentation faults. --- circle.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/circle.yml b/circle.yml index d7f983e499..111e078f20 100644 --- a/circle.yml +++ b/circle.yml @@ -26,3 +26,7 @@ test: - ./scripts/ci.sh : parallel: true timeout: 1200 + environment: + # Cleaning up temp directories at the end of a test run may be a + # contributing factor to segmentation faults, so don't bother. + METEOR_SAVE_TMPDIRS: 1 From 2ebd647e1a16c41344bc13533bd852bfd843620f Mon Sep 17 00:00:00 2001 From: Sashko Stubailo Date: Thu, 3 Aug 2017 09:25:52 -0700 Subject: [PATCH 41/53] Delete .reviewboardrc --- .reviewboardrc | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .reviewboardrc diff --git a/.reviewboardrc b/.reviewboardrc deleted file mode 100644 index 95c9b7f039..0000000000 --- a/.reviewboardrc +++ /dev/null @@ -1,2 +0,0 @@ -REVIEWBOARD_URL = 'https://rbcommons.com/s/meteor/' -REPOSITORY = 'Meteor framework' From 22e86ce20866c26cbfe2d68f3119de83a036cd97 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 3 Aug 2017 21:11:02 -0400 Subject: [PATCH 42/53] Add timeout to static-html test. --- tools/tests/static-html.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/tests/static-html.js b/tools/tests/static-html.js index fc17f67373..2fe6895a2b 100644 --- a/tools/tests/static-html.js +++ b/tools/tests/static-html.js @@ -54,7 +54,8 @@ selftest.define("static-html - throws error", () => { s.cd('myapp'); const run = startRun(s); - run.matchBeforeExit("Attributes on not supported"); + run.match("Attributes on not supported"); + run.waitSecs(90); run.stop(); }); From 3b2e0b6dbcd32ab8adebf1fb3ead69fe236da944 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Fri, 4 Aug 2017 20:00:09 +0300 Subject: [PATCH 43/53] Switch to using CircleCI 2.0 and various other test improvements. (#8985) This switches Meteor's CircleCI builds from Circle 1.0 to Circle 2.0 which has a bit more control over the workflow. Currently, this eliminates the existing ci.sh script which was already a bit incompatible when I was attempting to run Windows builds on another environment. It's possible that we should change this to a Node.js wrapper script. Other improvements: - We now store Core Dumps in build artifacts. CircleCI 2.0 advertised this as one of the features of CircleCI 2.0, but honestly, it was far from straightforward. Perhaps if we were using another Dockerimage, but it was far from as easy as flipping a switch. In addition to saving the Core Dump, this also saves the Node.js binary which was included in the Dev Bundle. This can be very handy for post-mortem debugging with tools like lldb, gdb, or mdb. - Memory usage is now logged throughout the build via a background process which logs `ps` output to a file which is persisted to the build artifacts. This should help identify if builds are terminating for some environmental reason. --- .circleci/config.yml | 413 +++++++++++++++++++++++++++++++++++++++++++ circle.yml | 28 --- scripts/ci.sh | 133 -------------- 3 files changed, 413 insertions(+), 161 deletions(-) create mode 100644 .circleci/config.yml delete mode 100644 circle.yml delete mode 100755 scripts/ci.sh diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000000..f3474cfcd3 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,413 @@ +version: 2 + +# These directories are cached across all builds, currently with no +# hashing mechanism, but we should consider doing it off dev_bundle. +meteor_cache_dirs: &meteor_cache_dirs + paths: + - "dev_bundle" + - ".babel-cache" + - ".meteor" + +# A reusable "run" snippet which is ran before each test to setup the +# environment for user-limits, core-dumps, etc. +run_env_change: &run_env_change + name: Environment Changes + command: | + # Make a place to core dumps to live. + sudo mkdir -p /tmp/core_dumps + sudo chmod a+rwx /tmp/core_dumps + + # Set the pattern for core dumps, so we can find them. + echo kernel.core_pattern="/tmp/core_dumps/core.%e.%p.%h.%t" | \ + sudo tee -a /etc/sysctl.conf + + # Note that since every "run" command starts its own shell, and I wasn't + # able to set this at a system wide level for all users, it's necessary to + # run "ulimit -c unlimited" before each command which you want to (possibly) + # output a core dump. + + # Raise inotify user watches up higher. + echo fs.inotify.max_user_watches=524288 | \ + sudo tee -a /etc/sysctl.conf + + # Reload sysctl so these are in effect. + sudo sysctl -p + +# A reusable "run" snippet which enables the continued logging of memoryusage +# to a file on disk which can be saved to build artifacts for later analysis. +run_log_mem_use: &run_log_mem_use + background: true + name: Setup Memory Logging + command: | + # Log memory usage throughout entire build. + MEMUSELOG=/tmp/memuse.txt /bin/bash -c '\ + while true; do\ + ps -u $USER eo pid,%cpu,%mem,rss:10,vsz:10,args:20 --sort=-%mem >> $MEMUSELOG; \ + echo "----------" >> $MEMUSELOG; \ + sleep 1; \ + done' + +# A reusable "run" snippet for saving the Node binary if a core dump is present. +run_save_node_bin: &run_save_node_bin + name: Save Node Binary + when: on_fail + command: | + if compgen -G "/tmp/core_dumps/core.*" > /dev/null; then + echo "Saving Node binary since Core dump is present..." + cp dev_bundle/bin/node /tmp/core_dumps/node + fi + +# This environment is set to every job (and the initial build). +build_machine_environment: &build_machine_environment + # Specify that we want an actual machine (ala Circle 1.0), not a Docker image. + machine: true + environment: + # This multiplier scales the waitSecs for selftests. + TIMEOUT_SCALE_FACTOR: 4 + + # These, mostly overlapping, flags ensure that CircleCI is as pretty as + # possible for a non-interactive environment. See also: --headless. + EMACS: t + METEOR_HEADLESS: true + METEOR_PRETTY_OUTPUT: 0 + + # In an effort to stop SIGSEGV, this just doesn't bother cleaning up + # the mess of temp directories that Meteor makes. + METEOR_SAVE_TMPDIRS: 1 + + # Disable the optimistic caching of file watchers, which incurs a slight + # polling delay which is less than ideal in a CI environment where file + # watchers should be plentiful. + METEOR_DISABLE_OPTIMISTIC_CACHING: 1 + + # Skip these tests on every test run. + # For readability, this is a regex wrapped across multiple lines in quotes. + SELF_TEST_EXCLUDE: "\ + ^old cli tests|\ + ^minifiers can't register non-js|\ + ^minifiers: apps can't use|\ + ^compiler plugins - addAssets\ + " + # These will be evaled before each command. + PRE_TEST_COMMANDS: |- + ulimit -c unlimited; # Set core dump size as Ubuntu 14.04 lacks prlimit. + ulimit -n 4096; # CircleCI default is soft 1024, hard 4096. Take it all. + + # Enable the Garbage Collection `gc` object to be exposed so we can try + # to our own, hopefully more graceful, technique. + TOOL_NODE_FLAGS: --expose-gc + + # This is only to make Meteor self-test not remind us that we can set + # this argument for self-tests. + SELF_TEST_TOOL_NODE_FLAGS: " " + +jobs: + Get Ready: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - checkout + - run: + # https://discuss.circleci.com/t/git-submodule-url-isnt-playing-nice-with-the-cache/549/3 + name: Git Submodules. + command: (git submodule sync && git submodule update --init --recursive) || (rm -fr .git/config .git/modules && git submodule deinit -f . && git submodule update --init --recursive) + - restore_cache: + key: meteor-cache + - run: + name: Get Ready + command: | + eval $PRE_TEST_COMMANDS; + ./meteor --help + # shouldn't take longer than 5 minutes + no_output_timeout: 5m + # Clear dev_bundle/.npm to ensure consistent test runs. + - run: + name: Clear npm cache + command: ./meteor npm cache clear + # Since PhantomJS has been removed from dev_bundle/lib/node_modules + # (#6905), but self-test still needs it, install it now. + - run: + name: Test Prereqs + command: ./meteor npm install -g phantomjs-prebuilt browserstack-webdriver + - run: + <<: *run_save_node_bin + - persist_to_workspace: + root: . + paths: . + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + save_caches: + <<: *build_machine_environment + steps: + - attach_workspace: + at: . + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + + Group 0: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running warehouse self-tests" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --with-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 1: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (1): A-Com" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^[a-b]|^c[a-n]|^co[a-l]|^compiler-plugins' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 2: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name "Running self-test (2): Con-K" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file "^co[n-z]|^c[p-z]|^[d-k]" \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 3: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (3): L-O" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^[l-o]' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 4: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (4): P" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^p' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 5: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (5): Run" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^run' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 6: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (6): R-S" + command: | + eval "$PRE_TEST_COMMANDS"; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^r(?!un)|^s' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + + Group 7: + <<: *build_machine_environment + steps: + - run: + <<: *run_log_mem_use + - run: + <<: *run_env_change + - attach_workspace: + at: . + - run: + name: "Running self-test (7): Sp-Z" + command: | + eval $PRE_TEST_COMMANDS; + ./meteor self-test \ + --exclude "${SELF_TEST_EXCLUDE}" \ + --headless \ + --file '^[t-z]|^command-line' \ + --without-tag "custom-warehouse" + no_output_timeout: 20m + - run: + <<: *run_save_node_bin + - save_cache: + key: meteor-cache + <<: *meteor_cache_dirs + - store_artifacts: + path: /tmp/core_dumps + - store_artifacts: + path: /tmp/memuse.txt + +workflows: + version: 2 + Build and Test: + jobs: + - Get Ready + - Group 0: + requires: + - Get Ready + - Group 1: + requires: + - Get Ready + - Group 2: + requires: + - Get Ready + - Group 3: + requires: + - Get Ready + - Group 4: + requires: + - Get Ready + - Group 5: + requires: + - Get Ready + - Group 6: + requires: + - Get Ready + - Group 7: + requires: + - Get Ready diff --git a/circle.yml b/circle.yml deleted file mode 100644 index d7f983e499..0000000000 --- a/circle.yml +++ /dev/null @@ -1,28 +0,0 @@ -checkout: - post: - # https://discuss.circleci.com/t/git-submodule-url-isnt-playing-nice-with-the-cache/549/3 - - git submodule sync - - git submodule update --init --recursive || (rm -fr .git/config .git/modules && git submodule deinit -f . && git submodule update --init --recursive) - -dependencies: - pre: - # https://github.com/meteor/docs/blob/version-NEXT/long-form/file-change-watcher-efficiency.md - - echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p - cache_directories: - - "dev_bundle" - - ".meteor" - - ".babel-cache" - override: - # shouldn't take longer than 5 minutes - - ./meteor --help: - timeout: 300 - environment: - METEOR_PRETTY_OUTPUT: 0 - METEOR_DISABLE_OPTIMISTIC_CACHING: 1 - TOOL_NODE_FLAGS: --expose-gc - -test: - override: - - ./scripts/ci.sh : - parallel: true - timeout: 1200 diff --git a/scripts/ci.sh b/scripts/ci.sh deleted file mode 100755 index 07b2824507..0000000000 --- a/scripts/ci.sh +++ /dev/null @@ -1,133 +0,0 @@ -#!/bin/sh - -# -# Optional Environment Variables for Configuration -# -# - TIMEOUT_SCALE_FACTOR: (default: 15) -# A multiplation factor that can be used to raise the wait-time on -# various longer-running tests. Useful for slower (or faster!) hardware. -# - ADDL_SELF_TEST_EXCLUDE: (optional) -# A regex or list of additional regexes to skip. - -# Export this one so it's available in the node environment. -export TIMEOUT_SCALE_FACTOR=${TIMEOUT_SCALE_FACTOR:-4} - -# Skip these tests always. Add other tests with ADDL_SELF_TEST_EXCLUDE. -SELF_TEST_EXCLUDE="^old cli tests|^minifiers can't register non-js|^minifiers: apps can't use|^compiler plugins - addAssets" - -# If no SELF_TEST_EXCLUDE is defined, use those defined here by default -if ! [ -z "$ADDL_SELF_TEST_EXCLUDE" ]; then - SELF_TEST_EXCLUDE="${SELF_TEST_EXCLUDE}|${ADDL_SELF_TEST_EXCLUDE}" -fi - -# Don't print as many progress indicators -export EMACS=t - -export METEOR_HEADLESS=true - -if [ -z "$CIRCLE_NODE_TOTAL" ] || [ -z "$CIRCLE_NODE_INDEX" ]; then - # In the case where these aren't set, just pretend like we're a single node. - # This is also handy if the user is using another CI service besides CircleCI - CIRCLE_NODE_TOTAL=1 - CIRCLE_NODE_INDEX=0 - - echo "[warn] CIRCLE_NODE_TOTAL or CIRCLE_NODE_INDEX was not defined. \c" - echo "Running all tests!" -fi - -# Clear dev_bundle/.npm to ensure consistent test runs. -./meteor npm cache clear - -# Since PhantomJS has been removed from dev_bundle/lib/node_modules -# (#6905), but self-test still needs it, install it now. -./meteor npm install -g phantomjs-prebuilt browserstack-webdriver - -# Make sure we have initialized and updated submodules such as -# packages/non-core/blaze. -git submodule update --init --recursive - -# run different jobs based on CicleCI parallel container index -should_run_test () { - test $(($1 % $CIRCLE_NODE_TOTAL)) -eq $CIRCLE_NODE_INDEX -} - -# Keep track of errors, but let the tests all finish. This is necessary since -# more than one of the following tests may be executed from a single run if -# parallelism is lower than the number of tests. -exit_code=0 - -# Also, if any uncaught errors slip through, fail the build. -set -e - -if should_run_test 0; then - echo "Running warehouse self-tests" - ./meteor self-test --headless \ - --with-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 1; then - echo "Running self-test (1): A-Com" - ./meteor self-test --headless \ - --file "^[a-b]|^c[a-n]|^co[a-l]|^compiler-plugins" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 2; then - echo "Running self-test (2): Con-K" - ./meteor self-test --headless \ - --file "^co[n-z]|^c[p-z]|^[d-k]" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 3; then - echo "Running self-test (3): L-O" - ./meteor self-test --headless \ - --file "^[l-o]" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 4; then - echo "Running self-test (4): P" - ./meteor self-test --headless \ - --file "^p" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 5; then - echo "Running self-test (5): Run" - ./meteor self-test --headless \ - --file "^run" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 6; then - echo "Running self-test (6): R-S" - ./meteor self-test --headless \ - --file "^r(?!un)|^s" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -if should_run_test 7; then - echo "Running self-test (7): Sp-Z" - ./meteor self-test --headless \ - --file "^[t-z]|^command-line" \ - --without-tag "custom-warehouse" \ - --exclude "$SELF_TEST_EXCLUDE" \ - || exit_code=$? -fi - -exit $exit_code From 6f259003eb91999b22f7681e581be1b9ceb92eb2 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 31 Jul 2017 19:30:26 -0400 Subject: [PATCH 44/53] Run all bare files before requiring eager entry point modules. --- tools/isobuild/linker.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index d117fb88f7..8e62c3e8db 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -398,6 +398,9 @@ _.extend(Module.prototype, { // Now that we have installed everything in this package or // application, immediately require the non-lazy modules and // evaluate the bare files. + + const eagerModuleFiles = []; + _.each(this.files, file => { if (file.bare) { chunks.push("\n", file.getPrelinkedOutput({ @@ -405,6 +408,12 @@ _.extend(Module.prototype, { noLineNumbers: this.noLineNumbers })); } else if (moduleCount > 0 && ! file.lazy) { + eagerModuleFiles.push(file); + } + }); + + if (eagerModuleFiles.length > 0) { + _.each(eagerModuleFiles, file => { if (file.mainModule) { exportsName = "exports"; } @@ -415,8 +424,8 @@ _.extend(Module.prototype, { JSON.stringify("./" + file.installPath), ");" ); - } - }); + }); + } return exportsName; } From a4d9f8c0f81cb2f6338e1ea4496e73551a7834ec Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Wed, 2 Aug 2017 15:13:18 -0400 Subject: [PATCH 45/53] Fix comments about bare files and add a note to History.md. --- History.md | 6 ++++++ tools/isobuild/linker.js | 15 +++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/History.md b/History.md index 113cb7a099..66611a6eab 100644 --- a/History.md +++ b/History.md @@ -24,6 +24,12 @@ [Issue #5121](https://github.com/meteor/meteor/issues/5121) [PR #8917](https://github.com/meteor/meteor/pull/8917) +* Files contained by `client/compatibility/` directories or added with + `api.addFiles(files, ..., { bare: true })` are now evaluated before + importing modules with `require`, which may be a breaking change if you + depend on the interleaving of `bare` files with eager module evaluation. + [PR #8972](https://github.com/meteor/meteor/pull/8972) + ## v1.5.1, 2017-07-12 * Node has been upgraded to version 4.8.4. diff --git a/tools/isobuild/linker.js b/tools/isobuild/linker.js index 8e62c3e8db..5a7b09fa60 100644 --- a/tools/isobuild/linker.js +++ b/tools/isobuild/linker.js @@ -233,8 +233,8 @@ _.extend(Module.prototype, { _.each(this.files, file => { if (file.bare) { - // Bare files will be added in between the synchronous require - // calls in _chunkifyEagerRequires. + // Bare files will be added before the synchronous require calls + // in _chunkifyEagerRequires. return; } @@ -384,10 +384,9 @@ _.extend(Module.prototype, { }, // Adds require calls to the chunks array for all modules that should be - // eagerly evaluated, and also includes bare files in the appropriate - // order with respect to the require calls. Returns the name of the - // variable that holds the main exports object, if api.mainModule was - // used to define a main module. + // eagerly evaluated, and also includes any bare files before the + // require calls. Returns the name of the variable that holds the main + // exports object, if api.mainModule was used to define a main module. _chunkifyEagerRequires(chunks, moduleCount, sourceWidth) { assert.ok(_.isArray(chunks)); assert.ok(_.isNumber(moduleCount)); @@ -396,8 +395,8 @@ _.extend(Module.prototype, { let exportsName; // Now that we have installed everything in this package or - // application, immediately require the non-lazy modules and - // evaluate the bare files. + // application, first evaluate the bare files, then require the + // non-lazy (eager) modules. const eagerModuleFiles = []; From 43ba3c9de5a792a32372d491785aa0bc9cabac1f Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 7 Aug 2017 12:43:37 -0400 Subject: [PATCH 46/53] Use unreleased version of websocket-driver that uses http-parser-js. https://github.com/faye/websocket-driver-node/issues/21 https://github.com/meteor/meteor-feature-requests/issues/160 Thanks to @sdarnell for identifying this solution. --- History.md | 6 ++++ .../.npm/package/npm-shrinkwrap.json | 35 ++++++++++--------- packages/ddp-client/package.js | 9 +++-- 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/History.md b/History.md index 4b32985f86..35cb93803c 100644 --- a/History.md +++ b/History.md @@ -23,6 +23,12 @@ * The `semver` npm package has been upgraded to version 5.3.0. [PR #8859](https://github.com/meteor/meteor/pull/8859) +* The `faye-websocket` npm package has been upgraded to version 0.11.1, + and its dependency `websocket-driver` has been upgraded to a version + containing [this fix](https://github.com/faye/websocket-driver-node/issues/21), + thanks to [@sdarnell](https://github.com/sdarnell). + [meteor-feature-requests#160](https://github.com/meteor/meteor-feature-requests/issues/160) + * The `star.json` manifest created within the root of a `meteor build` bundle will now contain `nodeVersion` and `npmVersion` which will specify the exact versions of Node.js and npm (respectively) which the Meteor release was diff --git a/packages/ddp-client/.npm/package/npm-shrinkwrap.json b/packages/ddp-client/.npm/package/npm-shrinkwrap.json index 9007897712..a44010e437 100644 --- a/packages/ddp-client/.npm/package/npm-shrinkwrap.json +++ b/packages/ddp-client/.npm/package/npm-shrinkwrap.json @@ -1,23 +1,14 @@ { "dependencies": { "faye-websocket": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.0.tgz", - "from": "faye-websocket@0.11.0", - "dependencies": { - "websocket-driver": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.6.4.tgz", - "from": "websocket-driver@>=0.5.1", - "dependencies": { - "websocket-extensions": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", - "from": "websocket-extensions@>=0.1.1" - } - } - } - } + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.1.tgz", + "from": "faye-websocket@0.11.1" + }, + "http-parser-js": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.5.tgz", + "from": "http-parser-js@>=0.4.0" }, "lolex": { "version": "1.4.0", @@ -28,6 +19,16 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/permessage-deflate/-/permessage-deflate-0.1.3.tgz", "from": "permessage-deflate@0.1.3" + }, + "websocket-driver": { + "version": "0.6.5", + "resolved": "https://github.com/faye/websocket-driver-node/tarball/1325828a9e8b5e29c7b4758995efdb84703919ad", + "from": "https://github.com/faye/websocket-driver-node/tarball/1325828a9e8b5e29c7b4758995efdb84703919ad" + }, + "websocket-extensions": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.1.tgz", + "from": "websocket-extensions@>=0.1.1" } } } diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 59667d5992..8afeb25c69 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -1,11 +1,16 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.0.0', + version: '2.1.0', documentation: null }); Npm.depends({ - "faye-websocket": "0.11.0", + "faye-websocket": "0.11.1", + // TODO Remove this direct websocket-driver dependency when a new + // version gets published, though that may not happen very soon: + // https://github.com/faye/websocket-driver-node/issues/21 + "websocket-driver": "https://github.com/faye/websocket-driver-node/" + + "tarball/1325828a9e8b5e29c7b4758995efdb84703919ad", "lolex": "1.4.0", "permessage-deflate": "0.1.3" }); From 77ecbabf60d8f8a694a38affc55857fa6e177ffb Mon Sep 17 00:00:00 2001 From: Jordan Brant Baker Date: Mon, 7 Aug 2017 10:23:56 -0700 Subject: [PATCH 47/53] Support "env" in .babelrc files. (#8963) --- packages/babel-compiler/babel-compiler.js | 10 ++++++++++ tools/tests/apps/modules/.babelrc | 7 +++++++ tools/tests/apps/modules/babel-env.js | 18 ++++++++++++++++++ tools/tests/apps/modules/package.json | 3 +++ 4 files changed, 38 insertions(+) create mode 100644 tools/tests/apps/modules/.babelrc create mode 100644 tools/tests/apps/modules/babel-env.js diff --git a/packages/babel-compiler/babel-compiler.js b/packages/babel-compiler/babel-compiler.js index c10289c199..ecc2428bb6 100644 --- a/packages/babel-compiler/babel-compiler.js +++ b/packages/babel-compiler/babel-compiler.js @@ -300,6 +300,16 @@ BCp._inferHelper = function ( merge(babelOptions, babelrc, "presets"); merge(babelOptions, babelrc, "plugins"); + const babelEnv = (process.env.BABEL_ENV || + process.env.NODE_ENV || + "development"); + if (babelrc && babelrc.env && babelrc.env[babelEnv]) { + const env = babelrc.env[babelEnv]; + walkBabelRC(env); + merge(babelOptions, env, "presets"); + merge(babelOptions, env, "plugins"); + } + return !! (babelrc.presets || babelrc.plugins); }; diff --git a/tools/tests/apps/modules/.babelrc b/tools/tests/apps/modules/.babelrc new file mode 100644 index 0000000000..f5e2cf5851 --- /dev/null +++ b/tools/tests/apps/modules/.babelrc @@ -0,0 +1,7 @@ +{ + "env": { + "development": { + "plugins": ["transform-do-expressions"] + } + } +} diff --git a/tools/tests/apps/modules/babel-env.js b/tools/tests/apps/modules/babel-env.js new file mode 100644 index 0000000000..3cc0035b6d --- /dev/null +++ b/tools/tests/apps/modules/babel-env.js @@ -0,0 +1,18 @@ +function babeltest() { + // use transform-do-expressions plugin to prove babel `env` subkey was loaded + let x = do { + 1; + }; + console.log(x) +} + +/* + If the plugin is loaded correctly there will be no errors during the compilation of this file. + Without this plugin you will get the error: + + W20170803-17:58:17.054(-7)? (STDERR) var x = do { + W20170803-17:58:17.055(-7)? (STDERR) ^^ + W20170803-17:58:17.055(-7)? (STDERR) + W20170803-17:58:17.055(-7)? (STDERR) SyntaxError: Unexpected token do +*/ + diff --git a/tools/tests/apps/modules/package.json b/tools/tests/apps/modules/package.json index fd63fa15cb..29a8f86116 100644 --- a/tools/tests/apps/modules/package.json +++ b/tools/tests/apps/modules/package.json @@ -19,5 +19,8 @@ "test": "METEOR_PROFILE=100 ../../../../meteor test --full-app --driver-package dispatch:mocha-phantomjs", "browser": "METEOR_PROFILE=100 ../../../../meteor test --full-app --driver-package dispatch:mocha-browser", "test-packages": "../../../../meteor test-packages --driver-package dispatch:mocha-phantomjs packages/modules-test-package" + }, + "devDependencies": { + "babel-plugin-transform-do-expressions": "^6.22.0" } } From 822ac2d7a401ce674112e5086f27c78b76606d9a Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 7 Aug 2017 13:28:44 -0400 Subject: [PATCH 48/53] Add a note to History.md about env in .babelrc files. --- History.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/History.md b/History.md index 35cb93803c..ccfed32a64 100644 --- a/History.md +++ b/History.md @@ -50,6 +50,9 @@ [#8424](https://github.com/meteor/meteor/issues/8424), and [#8464](https://github.com/meteor/meteor/issues/8464). +* The `"env"` field is now supported in `.babelrc` files. + [PR #8963](https://github.com/meteor/meteor/pull/8963) + ## v1.5.1, 2017-07-12 * Node has been upgraded to version 4.8.4. From b061f4b765b0544ca17295e17bff0df8b7a6f581 Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 4 Aug 2017 08:50:55 -0400 Subject: [PATCH 49/53] Increase mongo connection timeout to reduce self-test mongo errors. Certain self-test's like "modules - test app" are encountering mongo connection timeout errors on some runs. Increasing the connection timeout helps address these errors. --- tools/runners/run-mongo.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/runners/run-mongo.js b/tools/runners/run-mongo.js index e3cf6b3a53..b3d93ea412 100644 --- a/tools/runners/run-mongo.js +++ b/tools/runners/run-mongo.js @@ -593,7 +593,7 @@ var launchMongo = function (options) { 'meteor', new mongoNpmModule.Server('127.0.0.1', options.port, { poolSize: 1, - socketOptions: {connectTimeoutMS: 30000}, + socketOptions: {connectTimeoutMS: 60000}, }), {safe: true}); From b1fd243978e208a5b1ac70ee53167ad0485003de Mon Sep 17 00:00:00 2001 From: Hugh Willson Date: Fri, 4 Aug 2017 08:57:25 -0400 Subject: [PATCH 50/53] Reduce the "modules - test app" self-test start-up wait time. The increased mongo connection timeout in 522d86dc4e71c49241abe794bb01f05adc0882cc means that the we can decrease the "modules - test app" self-test application start-up wait internval significantly (since mongo will now start properly and the self-test can continue). --- tools/tests/modules.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/tests/modules.js b/tools/tests/modules.js index c9fac18c9b..5fa92593d2 100644 --- a/tools/tests/modules.js +++ b/tools/tests/modules.js @@ -24,7 +24,7 @@ selftest.define("modules - test app", function () { "--driver-package", "dispatch:mocha-phantomjs" ); - run.waitSecs(180); + run.waitSecs(60); run.match("App running at"); run.match("SERVER FAILURES: 0"); run.match("CLIENT FAILURES: 0"); From cc2a1d79e1582360ab1a6e12b5d9e1a62adfbdef Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 7 Aug 2017 14:59:53 -0400 Subject: [PATCH 51/53] Bump package versions for 1.5.2-beta.8 release. --- packages/accounts-base/package.js | 2 +- packages/babel-compiler/package.js | 2 +- packages/boilerplate-generator-tests/package.js | 2 +- packages/boilerplate-generator/package.js | 2 +- packages/ddp-client/package.js | 2 +- packages/ejson/package.js | 2 +- packages/meteor-tool/package.js | 2 +- packages/minimongo/package.js | 2 +- packages/modules/package.js | 2 +- packages/mongo/package.js | 2 +- packages/promise/package.js | 2 +- packages/webapp/package.js | 2 +- scripts/admin/meteor-release-experimental.json | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/accounts-base/package.js b/packages/accounts-base/package.js index 8d73182a09..30397d3f96 100644 --- a/packages/accounts-base/package.js +++ b/packages/accounts-base/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "A user account system", - version: "1.3.2-beta152.7" + version: "1.3.2-beta152.8" }); Package.onUse(function (api) { diff --git a/packages/babel-compiler/package.js b/packages/babel-compiler/package.js index 325a917e7a..fa6fc4a3fd 100644 --- a/packages/babel-compiler/package.js +++ b/packages/babel-compiler/package.js @@ -6,7 +6,7 @@ Package.describe({ // isn't possible because you can't publish a non-recommended // release with package versions that don't have a pre-release // identifier at the end (eg, -dev) - version: '6.20.0-beta152.7' + version: '6.20.0-beta152.8' }); Npm.depends({ diff --git a/packages/boilerplate-generator-tests/package.js b/packages/boilerplate-generator-tests/package.js index d2c3e859cc..a034c3ef39 100644 --- a/packages/boilerplate-generator-tests/package.js +++ b/packages/boilerplate-generator-tests/package.js @@ -2,7 +2,7 @@ Package.describe({ // These tests are in a separate package so that we can Npm.depend on // parse5, a html parsing library. summary: "Tests for the boilerplate-generator package", - version: '1.0.0-beta152.7', + version: '1.0.0-beta152.8', documentation: null }); diff --git a/packages/boilerplate-generator/package.js b/packages/boilerplate-generator/package.js index 29554e596e..8cf678633a 100644 --- a/packages/boilerplate-generator/package.js +++ b/packages/boilerplate-generator/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Generates the boilerplate html from program's manifest", - version: '1.2.0-beta152.7' + version: '1.2.0-beta152.8' }); Package.onUse(api => { diff --git a/packages/ddp-client/package.js b/packages/ddp-client/package.js index 8afeb25c69..616da29784 100644 --- a/packages/ddp-client/package.js +++ b/packages/ddp-client/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's latency-compensated distributed data client", - version: '2.1.0', + version: '2.1.0-beta152.8', documentation: null }); diff --git a/packages/ejson/package.js b/packages/ejson/package.js index 93c7d39a84..99e8652248 100644 --- a/packages/ejson/package.js +++ b/packages/ejson/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: 'Extended and Extensible JSON library', - version: '1.0.14-beta152.7' + version: '1.0.14-beta152.8' }); Package.onUse(function onUse(api) { diff --git a/packages/meteor-tool/package.js b/packages/meteor-tool/package.js index 32942c4778..f9d7a51ced 100644 --- a/packages/meteor-tool/package.js +++ b/packages/meteor-tool/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "The Meteor command-line tool", - version: "1.5.2-beta.7" + version: "1.5.2-beta.8" }); Package.includeTool(); diff --git a/packages/minimongo/package.js b/packages/minimongo/package.js index fab2ed036a..3282fc96ae 100644 --- a/packages/minimongo/package.js +++ b/packages/minimongo/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Meteor's client-side datastore: a port of MongoDB to Javascript", - version: '1.3.0-beta152.7' + version: '1.3.0-beta152.8' }); Package.onUse(function (api) { diff --git a/packages/modules/package.js b/packages/modules/package.js index 4d45c480a9..67f2e74430 100644 --- a/packages/modules/package.js +++ b/packages/modules/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "modules", - version: "0.10.0-beta152.7", + version: "0.10.0-beta152.8", summary: "CommonJS module system", documentation: "README.md" }); diff --git a/packages/mongo/package.js b/packages/mongo/package.js index 1c9c4689be..78ff2d98c3 100644 --- a/packages/mongo/package.js +++ b/packages/mongo/package.js @@ -9,7 +9,7 @@ Package.describe({ summary: "Adaptor for using MongoDB and Minimongo over DDP", - version: '1.2.0-beta152.7' + version: '1.2.0-beta152.8' }); Npm.depends({ diff --git a/packages/promise/package.js b/packages/promise/package.js index bf29ce1d36..b67d51ea7c 100644 --- a/packages/promise/package.js +++ b/packages/promise/package.js @@ -1,6 +1,6 @@ Package.describe({ name: "promise", - version: "0.9.0-beta152.7", + version: "0.9.0-beta152.8", summary: "ECMAScript 2015 Promise polyfill with Fiber support", git: "https://github.com/meteor/promise", documentation: "README.md" diff --git a/packages/webapp/package.js b/packages/webapp/package.js index 3958543cb4..dd8120fbd4 100644 --- a/packages/webapp/package.js +++ b/packages/webapp/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "Serves a Meteor app over HTTP", - version: '1.3.18-beta152.7' + version: '1.3.18-beta152.8' }); Npm.depends({connect: "2.30.2", diff --git a/scripts/admin/meteor-release-experimental.json b/scripts/admin/meteor-release-experimental.json index 14bc097004..d3df045d29 100644 --- a/scripts/admin/meteor-release-experimental.json +++ b/scripts/admin/meteor-release-experimental.json @@ -1,6 +1,6 @@ { "track": "METEOR", - "version": "1.5.2-beta.7", + "version": "1.5.2-beta.8", "recommended": false, "official": false, "description": "Meteor" From 6de5c25fed6398cfc600979f1a8aafa06b10ad80 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Mon, 7 Aug 2017 16:19:09 -0400 Subject: [PATCH 52/53] Enable transform-do-expressions in production for modules test app. The modules test app appears to be running with process.env.NODE_ENV equal to "production" on Circle CI: https://circleci.com/gh/meteor/meteor/5030. Enabling this transform in production as well as development is fine because we primarily want to test that plugins from the "env" section of .babelrc are respected, regardless of the value of process.env.NODE_ENV. Using different plugins in production might be worth testing, too, but that's less critical. Follow-up to #8963. --- tools/tests/apps/modules/.babelrc | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/tests/apps/modules/.babelrc b/tools/tests/apps/modules/.babelrc index f5e2cf5851..f385f93b62 100644 --- a/tools/tests/apps/modules/.babelrc +++ b/tools/tests/apps/modules/.babelrc @@ -2,6 +2,9 @@ "env": { "development": { "plugins": ["transform-do-expressions"] + }, + "production": { + "plugins": ["transform-do-expressions"] } } } From cfdc69bf717d3689e15bc7488a0a6dae30437cec Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Tue, 8 Aug 2017 18:01:30 -0400 Subject: [PATCH 53/53] Support @~ version constraints and use them for core packages. (#8991) --- .../constraint-solver/constraint-solver.js | 4 + .../package-version-parser.js | 37 ++++- tools/project-context.js | 5 +- .../packages/tilde-constraints/README.md | 0 .../packages/tilde-constraints/package.js | 11 ++ .../tilde-constraints/tilde-constraints.js | 1 + .../packages/tilde-dependent/README.md | 0 .../packages/tilde-dependent/package.js | 12 ++ .../tilde-dependent/tilde-dependent.js | 1 + tools/tests/package-tests.js | 146 ++++++++++++++++++ 10 files changed, 207 insertions(+), 10 deletions(-) create mode 100644 tools/tests/apps/package-tests/packages/tilde-constraints/README.md create mode 100644 tools/tests/apps/package-tests/packages/tilde-constraints/package.js create mode 100644 tools/tests/apps/package-tests/packages/tilde-constraints/tilde-constraints.js create mode 100644 tools/tests/apps/package-tests/packages/tilde-dependent/README.md create mode 100644 tools/tests/apps/package-tests/packages/tilde-dependent/package.js create mode 100644 tools/tests/apps/package-tests/packages/tilde-dependent/tilde-dependent.js diff --git a/packages/constraint-solver/constraint-solver.js b/packages/constraint-solver/constraint-solver.js index 8e4b78be21..435c312769 100644 --- a/packages/constraint-solver/constraint-solver.js +++ b/packages/constraint-solver/constraint-solver.js @@ -186,6 +186,10 @@ CS.isConstraintSatisfied = function (pkg, vConstraint, version) { var cVersion = simpleConstraint.versionString; return (cVersion === version); } else if (type === 'compatible-with') { + if (typeof simpleConstraint.test === "function") { + return simpleConstraint.test(version); + } + var cv = PV.parse(simpleConstraint.versionString); var v = PV.parse(version); diff --git a/packages/package-version-parser/package-version-parser.js b/packages/package-version-parser/package-version-parser.js index 8d179d357e..7d651f7edc 100644 --- a/packages/package-version-parser/package-version-parser.js +++ b/packages/package-version-parser/package-version-parser.js @@ -244,20 +244,41 @@ var parseSimpleConstraint = function (constraintString) { throw new Error("Non-empty string required"); } - var type, versionString; + var result = {}; + var needToCheckValidity = true; if (constraintString.charAt(0) === '=') { - type = "exactly"; - versionString = constraintString.substr(1); + result.type = "exactly"; + result.versionString = constraintString.slice(1); + } else { - type = "compatible-with"; - versionString = constraintString; + result.type = "compatible-with"; + + if (constraintString.charAt(0) === "~") { + var semversion = PV.parse( + result.versionString = constraintString.slice(1) + ).semver; + + var range = new semver.Range("~" + semversion); + + result.test = function (version) { + return range.test(PV.parse(version).semver); + }; + + // Already checked by calling PV.parse above. + needToCheckValidity = false; + + } else { + result.versionString = constraintString; + } } - // This will throw if the version string is invalid. - PV.getValidServerVersion(versionString); + if (needToCheckValidity) { + // This will throw if the version string is invalid. + PV.getValidServerVersion(result.versionString); + } - return { type: type, versionString: versionString }; + return result; }; diff --git a/tools/project-context.js b/tools/project-context.js index 971041f68e..e2071e80d1 100644 --- a/tools/project-context.js +++ b/tools/project-context.js @@ -748,8 +748,9 @@ _.extend(ProjectContext.prototype, { var constraint = utils.parsePackageConstraint( // Note that this used to be an exact name@=version constraint, // before #7084 eliminated these constraints completely. They - // were reinstated in Meteor 1.4.3 as name@version constraints. - packageName + "@" + version); + // were reinstated in Meteor 1.4.3 as name@version constraints, + // and further refined to name@~version constraints in 1.5.2. + packageName + "@~" + version); // Add a constraint but no dependency (we don't automatically use // all local packages!): depsAndConstraints.constraints.push(constraint); diff --git a/tools/tests/apps/package-tests/packages/tilde-constraints/README.md b/tools/tests/apps/package-tests/packages/tilde-constraints/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/tests/apps/package-tests/packages/tilde-constraints/package.js b/tools/tests/apps/package-tests/packages/tilde-constraints/package.js new file mode 100644 index 0000000000..7215c5d3e3 --- /dev/null +++ b/tools/tests/apps/package-tests/packages/tilde-constraints/package.js @@ -0,0 +1,11 @@ +Package.describe({ + name: "tilde-constraints", + version: "0.4.2", + summary: "Package for testing @~ version constraints", + documentation: "README.md" +}); + +Package.onUse(function(api) { + api.use("ecmascript"); + api.mainModule("tilde-constraints.js"); +}); diff --git a/tools/tests/apps/package-tests/packages/tilde-constraints/tilde-constraints.js b/tools/tests/apps/package-tests/packages/tilde-constraints/tilde-constraints.js new file mode 100644 index 0000000000..2a8dc005b2 --- /dev/null +++ b/tools/tests/apps/package-tests/packages/tilde-constraints/tilde-constraints.js @@ -0,0 +1 @@ +console.log(module.id); diff --git a/tools/tests/apps/package-tests/packages/tilde-dependent/README.md b/tools/tests/apps/package-tests/packages/tilde-dependent/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/tests/apps/package-tests/packages/tilde-dependent/package.js b/tools/tests/apps/package-tests/packages/tilde-dependent/package.js new file mode 100644 index 0000000000..2c0a869d87 --- /dev/null +++ b/tools/tests/apps/package-tests/packages/tilde-dependent/package.js @@ -0,0 +1,12 @@ +Package.describe({ + name: "tilde-dependent", + version: "0.1.0", + summary: "Package for testing inter-package @~ constraints", + documentation: "README.md" +}); + +Package.onUse(function(api) { + api.use("ecmascript"); + api.use("tilde-constraints"); + api.mainModule("tilde-dependent.js"); +}); diff --git a/tools/tests/apps/package-tests/packages/tilde-dependent/tilde-dependent.js b/tools/tests/apps/package-tests/packages/tilde-dependent/tilde-dependent.js new file mode 100644 index 0000000000..2a8dc005b2 --- /dev/null +++ b/tools/tests/apps/package-tests/packages/tilde-dependent/tilde-dependent.js @@ -0,0 +1 @@ +console.log(module.id); diff --git a/tools/tests/package-tests.js b/tools/tests/package-tests.js index 4985056957..a897734477 100644 --- a/tools/tests/package-tests.js +++ b/tools/tests/package-tests.js @@ -955,3 +955,149 @@ selftest.define("show readme excerpt", function () { run.matchErr("Documentation not found"); run.expectExit(1); }); + +selftest.define("tilde version constraints", [], function () { + var s = new Sandbox(); + + s.set("METEOR_WATCH_PRIORITIZE_CHANGED", "false"); + + s.createApp("tilde-app", "package-tests"); + s.cd("tilde-app"); + + var run = s.run(); + + run.match("tilde-app"); + run.match("proxy"); + run.waitSecs(10); + run.match("MongoDB"); + run.waitSecs(10); + run.match("your app"); + run.waitSecs(10); + run.match("running at"); + run.waitSecs(60); + + var packages = s.read(".meteor/packages") + .replace(/\n*$/m, "\n"); + + function setTopLevelConstraint(constraint) { + s.write( + ".meteor/packages", + packages + "tilde-constraints" + ( + constraint ? "@" + constraint : "" + ) + "\n" + ); + } + + setTopLevelConstraint(""); + run.match(/tilde-constraints.*added, version 0\.4\.2/); + run.match("tilde-constraints.js"); + run.waitSecs(10); + + setTopLevelConstraint("0.4.0"); + run.match("tilde-constraints.js"); + run.match("server restarted"); + run.waitSecs(10); + + setTopLevelConstraint("~0.4.0"); + run.match("tilde-constraints.js"); + run.match("server restarted"); + run.waitSecs(10); + + setTopLevelConstraint("0.4.3"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setTopLevelConstraint("~0.4.3"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setTopLevelConstraint("0.3.0"); + run.match("tilde-constraints.js"); + run.match("server restarted"); + run.waitSecs(10); + + setTopLevelConstraint("~0.3.0"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setTopLevelConstraint("0.5.0"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setTopLevelConstraint("~0.5.0"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + s.write( + ".meteor/packages", + packages + ); + run.match(/tilde-constraints.*removed/); + run.waitSecs(10); + + s.write( + ".meteor/packages", + packages + "tilde-dependent\n" + ); + run.match(/tilde-constraints.*added, version 0\.4\.2/); + run.match(/tilde-dependent.*added, version 0\.1\.0/); + run.match("tilde-constraints.js"); + run.match("tilde-dependent.js"); + run.waitSecs(10); + + var depPackageJsPath = "packages/tilde-dependent/package.js" + var depPackageJs = s.read(depPackageJsPath); + + function setDepConstraint(constraint) { + s.write( + depPackageJsPath, + depPackageJs.replace( + /tilde-constraints[^"]*/g, // Syntax highlighting hack: " + "tilde-constraints" + ( + constraint ? "@" + constraint : "" + ) + ) + ); + } + + setDepConstraint("0.4.0"); + run.match("tilde-constraints.js"); + run.match("tilde-dependent.js"); + run.match("server restarted"); + run.waitSecs(10); + + setDepConstraint("~0.4.0"); + run.match("tilde-constraints.js"); + run.match("tilde-dependent.js"); + run.match("server restarted"); + run.waitSecs(10); + + setDepConstraint("0.3.0"); + run.match("tilde-constraints.js"); + run.match("tilde-dependent.js"); + run.match("server restarted"); + run.waitSecs(10); + + // TODO The rest of these tests should cause version conflicts, but it + // seems like version constraints between local packages are ignored, + // which is a larger (preexisting) problem we should investigate. + /* + setDepConstraint("=0.4.0"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setDepConstraint("~0.3.0"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setDepConstraint("0.4.3"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + + setDepConstraint("~0.4.3"); + run.match("error: No version of tilde-constraints satisfies all constraints"); + run.waitSecs(10); + */ + + run.stop(); +});