Merge branch 'devel' into release-1.5.2

This commit is contained in:
Jesse Rosenberger
2017-08-02 22:31:14 +03:00
45 changed files with 586 additions and 686 deletions

View File

@@ -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
View File

@@ -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.

View File

@@ -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.

View File

@@ -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

View File

@@ -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).

View File

@@ -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.

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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');

View File

@@ -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));
};

View File

@@ -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)

View File

@@ -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/');

View File

@@ -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) {

View File

@@ -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);
});
}
}
});

View File

@@ -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

View File

@@ -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
///////////////////////////////////////////////////////////////////////////////

View File

@@ -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]

View File

@@ -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 = [];

View File

@@ -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 = [];

View File

@@ -508,6 +508,7 @@ var doInteractivePasswordLogin = function (options) {
} else {
loginFailed();
if (options.retry) {
delete loginData.password;
Console.error();
continue;
} else {

View File

@@ -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;

View File

@@ -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"),
);
}

View File

@@ -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);
});