From 7630fb7a357f18e366d364cd695bec4405fa4c0b Mon Sep 17 00:00:00 2001 From: skirunman Date: Thu, 13 Jul 2017 17:47:12 -0700 Subject: [PATCH 01/19] 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/19] [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/19] 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 a7c229d5e5415d407ec3dd42d26f56b43f297f59 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 16:14:30 +0300 Subject: [PATCH 04/19] 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 05/19] 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 d35f9b52ff0701c03e102ebda39c302e3b7d8eb4 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Thu, 27 Jul 2017 18:51:28 +0300 Subject: [PATCH 06/19] 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 07/19] 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 08/19] 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 09/19] 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 10/19] 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 11/19] 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 3cdcca6f6a3d944dfbb77da0894979cfca923869 Mon Sep 17 00:00:00 2001 From: Jesse Rosenberger Date: Mon, 31 Jul 2017 11:50:36 +0300 Subject: [PATCH 12/19] 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 c3dd664d14b469acb9b44f46d6f872bf61720da1 Mon Sep 17 00:00:00 2001 From: James Burgess Date: Wed, 2 Aug 2017 17:43:43 +0200 Subject: [PATCH 13/19] 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 14/19] 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 15/19] 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 16/19] 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 17/19] 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 18/19] 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 19/19] 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.