diff --git a/History.md b/History.md index 717c41519d..967f476a74 100644 --- a/History.md +++ b/History.md @@ -23,6 +23,13 @@ * The `semver` npm package has been upgraded to version 5.3.0. [PR #8859](https://github.com/meteor/meteor/pull/8859) +* 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/meteor b/meteor index 144420a751..dcbbf84a68 100755 --- a/meteor +++ b/meteor @@ -1,6 +1,6 @@ #!/usr/bin/env bash -BUNDLE_VERSION=4.8.29 +BUNDLE_VERSION=4.8.30 # OS Check. Put here because here is where we download the precompiled # bundles that are arch specific. 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 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..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({ @@ -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..9b0596bfa1 100644 --- a/packages/meteor/timers.js +++ b/packages/meteor/timers.js @@ -1,77 +1,86 @@ -var withoutInvocation = function (f) { +function withoutInvocation(f) { if (Package.ddp) { - var _CurrentMethodInvocation = Package.ddp.DDP._CurrentMethodInvocation; - if (_CurrentMethodInvocation.get() && _CurrentMethodInvocation.get().isSimulation) + 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 invocation = CurrentInvocation.get(); + if (invocation && invocation.isSimulation) { throw new Error("Can't set timers inside simulations"); - return function () { _CurrentMethodInvocation.withValue(null, f); }; - } - else + } + + return function () { + CurrentInvocation.withValue(null, f); + }; + } else { return f; -}; - -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. - - /** - * @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 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 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 function call scheduled by `Meteor.setTimeout`. - * @locus Anywhere - * @param {Object} id The handle returned by `Meteor.setTimeout` - */ - 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)); - - /** - * @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)); } -}); +} + +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 +// 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 + */ +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. + */ +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` + */ +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` + */ +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)); + +/** + * @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/'); 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); + }); + } } }); 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 diff --git a/tools/cli/commands.js b/tools/cli/commands.js index f6307b2891..5316264159 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 cb51a027e5..d397e731c7 100644 --- a/tools/cli/help.txt +++ b/tools/cli/help.txt @@ -511,16 +511,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/isobuild/bundler.js b/tools/isobuild/bundler.js index 5b077e8438..f2dd2d6d07 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 = []; 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 { diff --git a/tools/meteor-services/deploy.js b/tools/meteor-services/deploy.js index 53692cd668..e0ff7afcaa 100644 --- a/tools/meteor-services/deploy.js +++ b/tools/meteor-services/deploy.js @@ -4,14 +4,25 @@ // 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 { recordPackages } from './stats.js'; +import { Console } from '../console/console.js'; + +const hasOwn = Object.prototype.hasOwnProperty; const CAPABILITIES = ['showDeployMessages', 'canTransferAuthorization']; @@ -58,13 +69,13 @@ 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) { - options = _.clone(options); - options.headers = _.clone(options.headers || {}); +function deployRpc(options) { + options = Object.assign({}, options); + options.headers = Object.assign({}, options.headers || {}); 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(); @@ -74,7 +85,7 @@ var deployRpc = function (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', @@ -117,13 +128,13 @@ var deployRpc = function (options) { ret.message = body; } - var hasAllExpectedKeys = _.all(_.map( - options.expectPayload || [], function (key) { - return ret.payload && _.has(ret.payload, key); - })); + const hasAllExpectedKeys = + (options.expectPayload || []) + .map(key => ret.payload && hasOwn.call(ret.payload, key)) + .every(x => x); - 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; @@ -152,8 +163,8 @@ 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) { - var rpcOptions = _.clone(options); +function authedRpc(options) { + var rpcOptions = Object.assign({}, options); var preflight = rpcOptions.preflight; delete rpcOptions.preflight; @@ -178,7 +189,7 @@ var authedRpc = function (options) { username: username, suppressErrorMessage: true }; - if (auth.doInteractivePasswordLogin(loginOptions)) { + if (doInteractivePasswordLogin(loginOptions)) { return authedRpc(options); } else { return { @@ -198,45 +209,15 @@ var authedRpc = function (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?) 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')) { + if (! hasOwn.call(info, 'authorized')) { // Absence of this implies that we are not an authorized user on // this app if (preflight) { @@ -244,7 +225,7 @@ var authedRpc = function (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" : @@ -270,26 +251,11 @@ 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. -var printUnauthorizedMessage = function () { - var username = auth.loggedInUsername(); +function printUnauthorizedMessage() { + var username = loggedInUsername(); Console.error("Sorry, that site belongs to a different user."); if (username) { Console.error("You are currently logged in as " + username + "."); @@ -306,7 +272,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 @@ -359,7 +325,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; } @@ -379,10 +345,10 @@ var bundleAndDeploy = function (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. @@ -399,19 +365,14 @@ 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; } - 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...'); @@ -421,7 +382,7 @@ var bundleAndDeploy = function (options) { rootPath: process.cwd() }, function () { if (options.settingsFile) { - settings = files.getSettings(options.settingsFile); + settings = getSettings(options.settingsFile); } }); @@ -446,7 +407,7 @@ var bundleAndDeploy = function (options) { } if (options.recordPackageUsage) { - stats.recordPackages({ + recordPackages({ what: "sdk.deploy", projectContext: options.projectContext, site: site @@ -458,8 +419,8 @@ var bundleAndDeploy = function (options) { method: 'POST', operation: 'deploy', site: site, - qs: _.extend({}, options.rawOptions, settings !== null ? {settings: settings} : {}), - bodyStream: files.createTarGzStream(files.pathJoin(buildDir, 'bundle')), + qs: Object.assign({}, options.rawOptions, settings !== null ? {settings: settings} : {}), + bodyStream: createTarGzStream(pathJoin(buildDir, 'bundle')), expectPayload: ['url'], preflightPassword: preflight.preflightPassword, // Disable the HTTP timeout for this POST request. @@ -502,7 +463,7 @@ var bundleAndDeploy = function (options) { return 0; }; -var deleteApp = function (site) { +export function deleteApp(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -533,7 +494,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, @@ -547,15 +508,12 @@ 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()) { + 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 @@ -603,7 +561,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 @@ -619,7 +577,7 @@ var temporaryMongoUrl = function (site) { } }; -var logs = function (site) { +export function logs(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -631,12 +589,12 @@ var logs = function (site) { return 1; } else { Console.info(result.message); - auth.maybePrintRegistrationLink({ leadingNewline: true }); + maybePrintRegistrationLink({ leadingNewline: true }); return 0; } }; -var listAuthorized = function (site) { +export function listAuthorized(site) { site = canonicalizeSite(site); if (! site) { return 1; @@ -654,25 +612,20 @@ var listAuthorized = function (site) { } var info = result.payload; - if (! _.has(info, 'protection')) { + if (! hasOwn.call(info, 'protection')) { Console.info(""); return 0; } - if (info.protection === "password") { - 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; } - Console.info((auth.loggedInUsername() || "")); - _.each(info.authorized, function (username) { + Console.info((loggedInUsername() || "")); + 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 @@ -685,7 +638,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 @@ -715,89 +668,7 @@ 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 () { +export function listSites() { var result = deployRpc({ method: "GET", operation: "authorized-apps", @@ -815,10 +686,9 @@ var listSites = function () { ! 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; }; @@ -876,7 +746,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, @@ -894,17 +764,8 @@ 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; } - -exports.bundleAndDeploy = bundleAndDeploy; -exports.deleteApp = deleteApp; -exports.temporaryMongoUrl = temporaryMongoUrl; -exports.logs = logs; -exports.listAuthorized = listAuthorized; -exports.changeAuthorized = changeAuthorized; -exports.claim = claim; -exports.listSites = listSites; 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"), ); } 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); +});