mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'devel' into release-1.5.2
This commit is contained in:
@@ -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`
|
||||
|
||||
2
meteor
2
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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));
|
||||
};
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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/');
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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 <site>
|
||||
|
||||
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]
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -508,6 +508,7 @@ var doInteractivePasswordLogin = function (options) {
|
||||
} else {
|
||||
loginFailed();
|
||||
if (options.retry) {
|
||||
delete loginData.password;
|
||||
Console.error();
|
||||
continue;
|
||||
} else {
|
||||
|
||||
@@ -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("<anyone>");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (info.protection === "password") {
|
||||
Console.info("<password>");
|
||||
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() || "<you>"));
|
||||
_.each(info.authorized, function (username) {
|
||||
Console.info((loggedInUsername() || "<you>"));
|
||||
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 <username>"),
|
||||
Console.options({ indent: 2 }));
|
||||
Console.info();
|
||||
Console.info("Remove authorized users with:");
|
||||
Console.info(
|
||||
Console.command("meteor authorized " + site + " --remove <username>"),
|
||||
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;
|
||||
|
||||
@@ -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"),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user