Merge branch 'master' into devel

Conflicts:
	History.md
This commit is contained in:
Emily Stark
2014-02-24 14:22:44 -08:00
54 changed files with 1400 additions and 605 deletions

View File

@@ -15,11 +15,13 @@ GITHUB: codeinthehole <david.winterbottom@gmail.com>
GITHUB: dandv <ddascalescu+github@gmail.com>
GITHUB: DenisGorbachev <Denis.Gorbachev@faster-than-wind.ru>
GITHUB: emgee3 <hello@gravitronic.com>
GITHUB: EOT <eot@gmx.at>
GITHUB: FooBarWidget <honglilai@gmail.com>
GITHUB: jacott <geoffjacobsen@gmail.com>
GITHUB: Maxhodges <Max@whiterabbitpress.com>
GITHUB: meawoppl <meawoppl@gmail.com>
GITHUB: mitar <mitar.git@tnode.com>
GITHUB: mitar <mitar.github@tnode.com>
GITHUB: mizzao <mizzao@gmail.com>
GITHUB: mquandalle <maxime.quandalle@gmail.com>
GITHUB: nathan-muir <ndmuir@gmail.com>
@@ -28,6 +30,14 @@ GITHUB: ryw <ry@rywalker.com>
GITHUB: rzymek <rzymek@gmail.com>
GITHUB: sdarnell <stephen@darnell.plus.com>
GITHUB: timhaines <tmhaines@gmail.com>
GITHUB: jfhamlin <jfhamlin@gmail.com>
GITHUB: marcandre <github@marc-andre.ca>
GITHUB: michaelbishop <michael@michaelsplace.net>
GITHUB: OyoKooN <nathan@sxnlabs.com>
GITHUB: paulswartz <paulswartz@gmail.com>
GITHUB: rdickert <robert.dickert@gmail.com>
GITHUB: icellan <icellan@icellan.com>
GITHUB: yeputons <egor.suvorov@gmail.com>
METEOR: avital <avital@thewe.net>
METEOR: debergalis <matt@meteor.com>
@@ -40,3 +50,5 @@ METEOR: karayu <lele.yu@gmail.com>
METEOR: n1mmy <nim@meteor.com>
METEOR: sixolet <naomi@meteor.com>
METEOR: Slava <slava@meteor.com>
METEOR: stubailo <sashko@mit.edu>
METEOR: ekatek <ekate@meteor.com>

View File

@@ -1,85 +1,57 @@
## v.NEXT
* Meteor developer accounts
- `accounts-meteor-developer` package for OAuth support
- managing deployed apps with developer accounts instead of site
passwords.
- New commands: 'meteor authorized', 'meteor claim', 'meteor logout',
'meteor whoami'
## v0.7.1.1
* oplog improvements
- support all operators except $where and $near. still not used for
limit and skip
- more optimizations to avoid needless data fetches from MongoDB
- fix "Cannot call method 'has' of null" error #1767
* Integrate with Meteor developer accounts, a new way of managing your
meteor.com deployed sites. When you use `meteor deploy`, you will be
prompted to create a developer account.
- Once you've created a developer account, you can log in and out
from the command line with `meteor login` and `meteor logout`.
- You can claim legacy sites with `meteor claim`. This command will
prompt you for your site password if you are claiming a
password-protected site; after claiming it, you will not need to
enter the site password again.
- You can add or remove authorized users, and view the list of
authorized users, for a site with `meteor authorized`.
- You can view your current username with `meteor whoami`.
- This release also includes the `accounts-meteor-developer` package
for building Meteor apps that allow users to log in with their own
developer accounts.
* Minimongo improvements
- support $comment
- support 'obj' name in $where
- $regexp matches actual regexps properly
- better support for $nin, $ne, $not
- support using { $in: [/foo/, /bar/] }. #1707
- support {$exists: false}
- better type-checking for selectors
- support {x: {$elemMatch: {$gt: 5}}}
- match mongo's behavior better when there are arrays in the document
- support $near with sort
- implement updates with { $set: { 'a.$.b': 5 } }
- {$type: 4} queries
- optimize `remove({})` when observers are paused
- make update-by-id constant time
* Improve the oplog tailing implementation for getting real-time database
updates from MongoDB.
- Add support for all operators except `$where` and `$near`. Limit and
skip are not supported yet.
- Add optimizations to avoid needless data fetches from MongoDB.
- Fix an error ("Cannot call method 'has' of null") in an oplog
callback. #1767
* Add `clientAddress` and `httpHeaders` to `this.connection` in method
calls and publish functions.
* Add and improve support for minimongo operators.
- Support `$comment`.
- Support `obj` name in `$where`.
- `$regexp` matches actual regexps properly.
- Improve support for `$nin`, `$ne`, `$not`.
- Support using `{ $in: [/foo/, /bar/] }`. #1707
- Support `{$exists: false}`.
- Improve type-checking for selectors.
- Support `{x: {$elemMatch: {$gt: 5}}}`.
- Match Mongo's behavior better when there are arrays in the document.
- Support `$near` with sort.
- Implement updates with `{ $set: { 'a.$.b': 5 } }`.
- Support `{$type: 4}` queries.
- Optimize `remove({})` when observers are paused.
- Make update-by-id constant time.
- Allow `{$set: {'x._id': 1}}`. #1794
* Hash login tokens before storing them in the database. Legacy unhashed
tokens are upgraded to hashed tokens in the database as they are used
in logins.
* Cursors with a field specifier containing `{_id: 0}` can no longer be used
with `observeChanges` or `observe`. This includes the implicit calls to these
functions that are done when returning a cursor from a publish function or
using `{{#each}}`.
* Transform functions must return objects and may not change the `_id` field
(though they may leave it out)
* XXX sourcemaps support for stylesheets, including less sourcemaps
* XXX css linting (breaks on errors)
* XXX css preprocessing to concatenate files correctly (pulls @imports to the
beginning)
* XXX supports `.import.less` and `.import.styl` to prevent Meteor processing
stylesheets. `.lessimport` is deprecated
* Patch Underscore to not treat plain objects (`x.constructor === Object`)
with numeric `length` fields as arrays. Among other things, this allows you
to use documents with numeric `length` fields with Mongo. #594 #1737
* Fix races when calling login and/or logoutOtherClients from multiple
tabs. #1616
* Upgrade `jquery-waypoints` package from 1.1.7 to 2.0.4. (Contains
backward-incompatible changes).
* Add `frame-src` to `browser-policy-content` and account for
cross-browser CSP disparities.
* Upgrade CoffeeScript from 1.6.3 to 1.7.1.
* Make sure that `api.add_files('foo.coffee', {bare: true})` works when
adding CoffeeScript files. #1668
* `force-ssl`: don't require SSL during `meteor run` in IPv6
environments. #1751
* Upgraded dependencies:
- node from 0.10.22 to 0.10.25 (removed workaround from 0.7.0 -- now
support 0.10.25+)
- Upgrade jQuery from 1.8.2 to 1.11.0
XXX see http://jquery.com/upgrade-guide/1.9/ for incompatibilities (maybe
goes in notices?)
- source-map from 0.3.30 to 0.3.32 #1782
- websocket-driver from 0.3.1 to 0.3.2
* Upgraded dependencies
- node: 0.10.25 (from 0.10.22). The workaround for specific Node
versions from 0.7.0 is now removed; 0.10.25+ is supported.
- jquery: 1.11.0 (from 1.8.2). See
http://jquery.com/upgrade-guide/1.9/ for upgrade instructions.
- jquery-waypoints: 2.0.4 (from 1.1.7). Contains
backwards-incompatible changes.
- source-map: 0.3.2 (from 0.3.30) #1782
- websocket-driver: 0.3.2 (from 0.3.1)
- http-proxy: 1.0.2 (from a pre-release fork of 1.0)
- semver: 2.2.1 (from 2.1.0)
- request: 2.33.0 (from 2.27.0)
@@ -90,73 +62,138 @@
- source-map-support: 0.2.5 (from 0.2.3)
- mongo: 2.4.9 (from 2.4.8)
- openssl in mongo: 1.0.1f (from 1.0.1e)
- kexec from 0.1.1 to 0.2.0
- drop shell-quote from dev bundle
- XXX upgraded `less` from 1.3.3 to 1.6.1
- XXX upgraded `stylus` from 0.37.0 to 0.42.2 and `nib` from `1.0.0` to `1.0.2`
- kexec: 0.2.0 (from 0.1.1)
- less: 1.6.1 (from 1.3.3)
- stylus: 0.42.2 (from 0.37.0)
- nib: 1.0.2 (from 1.0.0)
- coffeescript: 1.7.1 (from 1.6.3)
* Types added with `EJSON.addType` now have default `clone` and `equals`
implementations. #1745
* CSS preprocessing and sourcemaps:
- Add sourcemap support for CSS stylesheet preprocessors. Use
sourcemaps for stylesheets compiled with LESS.
- Improve CSS minification to deal with `@import` statements correctly.
- Lint CSS files for invalid `@` directives.
- Change the recommended suffix for imported LESS files from
`.lessimport` to `.import.less`. Add `.import.styl` to allow
`stylus` imports. `.lessimport` continues to work but is deprecated.
* Allow cursors on named local collections to be returned from arrays in publish
functions. #1820
* Add `clientAddress` and `httpHeaders` to `this.connection` in method
calls and publish functions.
* Don't lose permissions (eg, executable bit) on npm files. #1808
* Detect if CSS failed to load and refresh.
* Don't crash with an empty programs/foo directory or one without a
package.js.
* Do a better job of handling shrinkwrap files when a npm module depends
on something that isn't a semver. #1684
* Fix failures updating npm dependencies when a node_modules directory exists
above the project directory. #1761
* In email package, print a message in dev mode when email is not sent. #1196
* Meteor accounts logins (or anything else using the `localstorage` package) no
longer persist in IE7.
* Fix `accounts-password` login with private-browsing Safari (and
generally, the use of the `localstorage` package). #1291
* New `retry` package.
* Pass through `update` and `remove` return values for validated
operations. #1759
* Hash login tokens before storing them in the database. Legacy unhashed
tokens are upgraded to hashed tokens in the database as they are used
in login requests.
* Change default accounts-ui styling and add more CSS classes.
* Don't leak sockets on error in dev-mode proxy.
* Refactor command-line tool. Add test harness and better tests. Run
`meteor self-test --help` for info on running the tools test suite.
* XXX Make springboard actually exec
* Speed up application re-build in development mode by re-using file
hash computation between file change watching code and application
build code..
* Speed up build by re-using file hashes.
* Fix issues with documents containing a key named `length` with a
numeric value. Underscore treated these as arrays instead of objects,
leading to exceptions when . Patch Underscore to not treat plain
objects (`x.constructor === Object`) with numeric `length` fields as
arrays. #594 #1737
* Include oauth_verifier as a header rather than a parameter in
`oauth1` package. #1825
* Deprecate `Accounts.loginServiceConfiguration` in favor of
`ServiceConfiguration.configurations`, exported by the
`service-configuration` package. `Accounts.loginServiceConfiguration`
is maintained for backwards-compatibility, but it is defined in a
`Meteor.startup` block and so cannot be used from top-level code.
* Refactor command-line tool. Add test harness and better tests.
* Cursors with a field specifier containing `{_id: 0}` can no longer be
used with `observeChanges` or `observe`. This includes the implicit
calls to these functions that are done when returning a cursor from a
publish function or using `{{#each}}`.
* Add `Accounts.connection` for using Meteor accounts packages with a
non-default DDP connection.
* Transform functions must return objects and may not change the `_id`
field, though they may leave it out.
* Fix order of setting login token and setting user id on a connection.
* Remove broken IE7 support from the `localstorage` package. Meteor
accounts logins no longer persist in IE7.
* Fix the `localstorage` package when used with Safari in private
browsing mode. This fixes a problem with login token storage and
account login. #1291
* Types added with `EJSON.addType` now have default `clone` and `equals`
implementations. Users may still specify `clone` or `equals` functions
to override the default behavior. #1745
* Add `frame-src` to `browser-policy-content` and account for
cross-browser CSP disparities.
* Deprecate `Oauth.initiateLogin` in favor of `Oauth.showPopup`.
* Add `WebApp.rawConnectHandlers` for adding connect handlers that run
before any other Meteor handlers, except `connect.compress()`. Raw
connect handlers see the URL's full path (even if ROOT_URL contains a
non-empty path) and they run before static assets are served.
* Add `Accounts.connection` to allow using Meteor accounts packages with
a non-default DDP connection.
* Detect and reload if minified CSS files fail to load at startup. This
prevents the application from running unstyled if the page load occurs
while the server is switching versions.
* Allow Npm.depends to specify any http or https URL containing a full
40-hex-digit SHA. #1686
* Add `retry` package for connection retry with exponential backoff.
* Pass `update` and `remove` return values correctly when using
collections validated with `allow` and `deny` rules. #1759
* If you're using Deps on the server, computations and invalidation
functions are not allowed to yield.
functions are not allowed to yield. Throw an error instead of behaving
unpredictably.
* Fix namespacing in coffeescript files added to a package with the
`bare: true` option. #1668
* Fix races when calling login and/or logoutOtherClients from multiple
tabs. #1616
* Include oauth_verifier as a header rather than a parameter in
the `oauth1` package. #1825
* Fix `force-ssl` to allow local development with `meteor run` in IPv6
environments. #1751`
* Allow cursors on named local collections to be returned from a publish
function in an array. #1820
* Fix build failure caused by a directory in `programs/` without a
package.js file.
* Do a better job of handling shrinkwrap files when an npm module
depends on something that isn't a semver. #1684
* Fix failures updating npm dependencies when a node_modules directory
exists above the project directory. #1761
* Preserve permissions (eg, executable bit) on npm files. #1808
* SockJS tweak to support relative base URLs.
* Oauth.initiateLogin is deprecated in favor of Oauth.showPopup.
* Don't leak sockets on error in dev-mode proxy.
* User-supplied connect handlers now see the URL's full path, even if
ROOT_URL contains a non-empty path.
* Clone arguments to `added` and `changed` methods in publish
functions. This allows callers to reuse objects and prevents already
published data from changing after the fact. #1750
* Don't cache direct references to the fields arguments to the subscription
`added` and `changed` methods. #1750
* Ensure springboarding to a different meteor tools version always uses
`exec` to run the old version. This simplifies process management for
wrapper scripts.
Patches contributed by GitHub users DenisGorbachev, EOT, OyoKooN, awwx,
dandv, icellan, jfhamlin, marcandre, michaelbishop, mitar, mizzao,
mquandalle, paulswartz, rdickert, rzymek, timhaines, and yeputons.
## v0.7.0.1

View File

@@ -71,6 +71,13 @@ handlebars: https://github.com/wycats/handlebars.js/
Copyright (C) 2011 by Yehuda Katz
----------
clean-css: https://github.com/GoalSmashers/clean-css
----------
Copyright (c) 2011 GoalSmashers.com
----------
progress: https://github.com/visionmedia/node-progress
qs: https://github.com/visionmedia/node-querystring
@@ -285,6 +292,7 @@ deep-equal: https://github.com/substack/node-deep-equal
editor: https://github.com/substack/node-editor
minimist: https://github.com/substack/minimist
quotemeta: https://github.com/substack/quotemeta
text-table: https://github.com/substack/text-table
----------
Copyright 2010, 2011, 2012, 2013 James Halliday (mail@substack.net)
@@ -587,7 +595,7 @@ Copyright 2009 Google Inc.
----------
request: https://github.com/mikeal/request
aws-sign: https://github.com/mikeal/aws-sign
aws-sign2: https://github.com/mikeal/aws-sign
cookie-jar: https://github.com/mikeal/cookie-jar
forever-agent: https://github.com/mikeal/forever-agent
oauth-sign: https://github.com/mikeal/oauth-sign
@@ -646,6 +654,35 @@ Diff Match and Patch: http://code.google.com/p/google-diff-match-patch/
Copyright 2006 Google Inc.
----------
ansicolors: https://github.com/thlorenz/ansicolors
ansistyles: https://github.com/thlorenz/ansistyles
----------
Copyright 2013 Thorsten Lorenz.
----------
columnify: https://github.com/timoxley/columnify
----------
Copyright Tim Oxley
----------
eventemitter3: https://github.com/3rd-Eden/EventEmitter3
----------
Copyright Arnout Kazemier
----------
punycode: https://github.com/bestiejs/punycode.js
----------
Copyright Mathias Bynens <http://mathiasbynens.be/>
============
BSD Licenses
@@ -1346,6 +1383,25 @@ required for its use.
Other
=====
----------
heapdump: https://github.com/bnoordhuis/node-heapdump
----------
Copyright (c) 2012, Ben Noordhuis <info@bnoordhuis.nl>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
----------
mimelib-noiconv: https://github.com/andris9/mimelib
mailcomposer: https://github.com/andris9/mailcomposer
@@ -1396,6 +1452,7 @@ By Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me/)
----------
opener: https://github.com/domenic/opener
path-is-inside: https://github.com/domenic/path-is-inside
----------
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
@@ -1444,6 +1501,91 @@ OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
----------
tough-cookie: https://github.com/goinstant/tough-cookie
----------
Copyright GoInstant, Inc. and other contributors. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
The following exceptions apply:
===
`pubSufTest()` of generate-pubsuffix.js is in the public domain.
// Any copyright is dedicated to the Public Domain.
// http://creativecommons.org/publicdomain/zero/1.0/
===
`public-suffix.txt` was obtained from
<https://mxr.mozilla.org/mozilla-central/source/netwerk/dns/effective_tld_names.dat?raw=1>
via <http://publicsuffix.org>.
That file contains the usual Mozilla triple-license, for which this project uses it
under the terms of the MPL 1.1:
// ***** BEGIN LICENSE BLOCK *****
// Version: MPL 1.1/GPL 2.0/LGPL 2.1
//
// The contents of this file are subject to the Mozilla Public License Version
// 1.1 (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
// for the specific language governing rights and limitations under the
// License.
//
// The Original Code is the Public Suffix List.
//
// The Initial Developer of the Original Code is
// Jo Hermans <jo.hermans@gmail.com>.
// Portions created by the Initial Developer are Copyright (C) 2007
// the Initial Developer. All Rights Reserved.
//
// Contributor(s):
// Ruben Arakelyan <ruben@rubenarakelyan.com>
// Gervase Markham <gerv@gerv.net>
// Pamela Greene <pamg.bugs@gmail.com>
// David Triendl <david@triendl.name>
// Jothan Frakes <jothan@gmail.com>
// The kind representatives of many TLD registries
//
// Alternatively, the contents of this file may be used under the terms of
// either the GNU General Public License Version 2 or later (the "GPL"), or
// the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
// in which case the provisions of the GPL or the LGPL are applicable instead
// of those above. If you wish to allow use of your version of this file only
// under the terms of either the GPL or the LGPL, and not to allow others to
// use your version of this file under the terms of the MPL, indicate your
// decision by deleting the provisions above and replace them with the notice
// and other provisions required by the GPL or the LGPL. If you do not delete
// the provisions above, a recipient may use your version of this file under
// the terms of any one of the MPL, the GPL or the LGPL.
//
// ***** END LICENSE BLOCK *****
----------
MongoDB: http://www.mongodb.org/
----------

View File

@@ -1 +1 @@
sso-1
0.7.1.1

View File

@@ -1629,6 +1629,16 @@ This function is provided by the `accounts-password` package. See the
{{> api_box loginWithExternalService}}
Available functions are:
* `Meteor.loginWithMeteorDeveloperAccount`
* `Meteor.loginWithFacebook`
* `Meteor.loginWithGithub`
* `Meteor.loginWithGoogle`
* `Meteor.loginWithMeetup`
* `Meteor.loginWithTwitter`
* `Meteor.loginWithWeibo`
These functions initiate the login process with an external
service (eg: Facebook, Google, etc), using OAuth. When called they open a new pop-up
window that loads the provider's login page. Once the user has logged in
@@ -1659,14 +1669,15 @@ External login services typically require registering and configuring
your application before use. The easiest way to do this is with the
[`accounts-ui` package](#accountsui) which presents a step-by-step guide
to configuring each service. However, the data can be also be entered
manually in the `Accounts.loginServiceConfiguration` collection. For
example:
manually in the `ServiceConfiguration.configurations` collection, which
is exported by the `service-configuration` package. For example, after
running `meteor add service-configuration` in your app:
// first, remove configuration entry in case service is already configured
Accounts.loginServiceConfiguration.remove({
ServiceConfiguration.configurations.remove({
service: "weibo"
});
Accounts.loginServiceConfiguration.insert({
ServiceConfiguration.configurations.insert({
service: "weibo",
clientId: "1292962797",
secret: "75a730b58f5691de5522789070c319bc"

View File

@@ -201,13 +201,7 @@ var toc = [
"Meteor.logout",
"Meteor.logoutOtherClients",
"Meteor.loginWithPassword",
{name: "Meteor.loginWithMeteorDeveloperAccount", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithFacebook", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithGithub", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithGoogle", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithMeetup", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithTwitter", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWithWeibo", id: "meteor_loginwithexternalservice"},
{name: "Meteor.loginWith<Service>", id: "meteor_loginwithexternalservice"},
{type: "spacer"},
{name: "{{currentUser}}", id: "template_currentuser"},

View File

@@ -1,5 +1,5 @@
// While galaxy apps are on their own special meteor releases, override
// Meteor.release here.
if (Meteor.isClient) {
Meteor.release = Meteor.release ? "0.7.0.1" : undefined;
Meteor.release = Meteor.release ? "0.7.1.1" : undefined;
}

View File

@@ -1 +1 @@
0.7.0.1
0.7.1.1

View File

@@ -1 +1 @@
0.7.0.1
0.7.1.1

View File

@@ -1 +1 @@
0.7.0.1
0.7.1.1

View File

@@ -1 +1 @@
0.7.0.1
0.7.1.1

View File

@@ -1,6 +1,5 @@
Package.describe({
summary: "Login service for Meteor developer accounts",
internal: true // XXX for now
summary: "Login service for Meteor developer accounts"
});
Package.on_use(function (api) {

View File

@@ -43,7 +43,7 @@ Template._loginButtonsLoggedOutSingleLoginButton.capitalizedName = function () {
// XXX we should allow service packages to set their capitalized name
return 'GitHub';
else if (this.name === 'meteor-developer')
return 'a Meteor developer account';
return 'Meteor';
else
return capitalize(this.name);
};

View File

@@ -1,5 +1,5 @@
Package.describe({
summary: "Automatically publish the entire database to all clients"
summary: "Publish the entire database to all clients"
});
// This package is empty; its presence is detected by livedata and

View File

@@ -1 +0,0 @@
.build*

View File

@@ -1,59 +0,0 @@
Cookies = {};
// Given the value of a Cookie header, returns a dictionary of cookie
// keys => values. If passed the empty string (or a string that is all
// whitespace), returns {}
//
// cookieString is the value of document.cookies or a Cookie header,
// for example "a=b; c=d".
Cookies.parse = function (cookieString) {
var cookies = {};
var cookieParts = cookieString.split(/\s*;\s*/);
_.each(cookieParts, function (part) {
var match = part.match(/^([^=]+)=(.*)/);
if (match)
// Browsers are not supposed to send multiple values for the
// same cookie, but if they do, do the easy thing, which is to
// take the last value seen.
cookies[match[1]] = match[2];
});
return cookies;
};
// Given a dictionary of cookie names and values, return a Cookie
// header (as parsed by Cookies.parse).
//
// No attempt is made to sanitize or quote characters in the cookie
// name or value. Behavior varies between browsers, between RFCs, and
// between browsers and RFCs. If you want to play it safe, good advice
// would be to limit cookie names to alphanumerics, dashes, and
// underscores, and limit cookie values to printable ASCII characters
// excluding quote, comma, semicolon, backspace, and whitespace.
Cookies.stringify = function (cookies) {
// RFC6265 says that valid characters in a cookie name are:
// token = <token, defined in [RFC2616], Section 2.2>
// RFC2616 is HTTP 1.1 and defines 'token' as:
// token = 1*<any CHAR except CTLs or separators>
// separators = "(" | ")" | "<" | ">" | "@"
// | "," | ";" | ":" | "\" | <">
// | "/" | "[" | "]" | "?" | "="
// | "{" | "}" | SP | HT
//
// RFC6265 says that valid characters in a cookie value are:
// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
// ; US-ASCII characters excluding CTLs,
// ; whitespace DQUOTE, comma, semicolon,
// ; and backslash
//
// In practice, browsers (at least Chrome) permit a wider range of
// characters in cookie values (such as, in Chome, at least comma
// and double quote). So for now we're going to not worry about this
// and trust the user to know what characters their targeted
// browsers tolerate.
return _.map(cookies, function (value, key) {
return key + "=" + value;
}).join(";");
};

View File

@@ -1,55 +0,0 @@
// If the current origin (and path) has a (non-HttpOnly) cookie for
// 'name', return it. Otherwise return null.
Cookies.get = function (name) {
var cookies = Cookies.parse(document.cookie || '');
return _.has(cookies, name) ? cookies[name] : null;
};
// Set a cookie 'name'='value on the current origin. It is the
// caller's responsibility to ensure that 'name' and 'value' contain
// only characters that are legal in cookie names and values.
//
// Options may include:
//
// - path: Path prefix for which the cookie should be sent. If not
// specified, defaults to the current path of document.location.
//
// - domain: Domain for which the cookie should be sent. Default to
// the current domain (more precisely, the host part of the current
// document.location). Use ".mysite.com" to send to mysite.com and
// all of mysite's subdomains.
//
// - maxAge: How long the cookie should live (in seconds). If not
// provided, the cookie will expire at the end of the browser
// session.
//
// - secure: If true, provide this cookie only for secure (https)
// connections. If you call this from a page that was loaded over
// http, the cookie will be set but you won't be able to read it
// back unless the user reloads the page over https.
//
// To delete a cookie, set maxAge to zero, passing the same name,
// domain, and path.
Cookies.set = function (name, value, options) {
options = options || {};
var cmd = name + '=' + value;
if (_.has(options, 'path'))
cmd += ";path=" + options.path;
if (_.has(options, 'domain'))
cmd += ";domain=" + options.domain;
if (_.has(options, "maxAge")) {
// Not all browsers support 'max-age', but all support 'expires'.
var when = new Date((new Date).getTime() + options.maxAge * 1000);
cmd += ";expires=" + when.toUTCString();
}
if (_.has(options, "secure"))
cmd += ";secure";
// This does not set document.cookie. It causes the browser to
// behave as if it had received a Set-Cookie header with the value
// 'cmd'.
//
// "This is the worst interface I have ever seen in my life." -- Emily
document.cookie = cmd;
};

View File

@@ -1,14 +0,0 @@
Tinytest.add("cookies - browser set/get", function (test) {
var name = Random.id();
test.equal(Cookies.get(name), null);
Cookies.set(name, "hello");
test.equal(Cookies.get(name), "hello");
Cookies.set(name, "hello again");
test.equal(Cookies.get(name), "hello again");
Cookies.set(name, "stuff", { maxAge: 0 });
test.equal(Cookies.get(name), null);
Cookies.set(name, "kitten", { path: "/somewhere-else" });
test.equal(Cookies.get(name), null);
Cookies.set(name, "kitten", { path: "/somewhere-else", maxAge: 0 });
});

View File

@@ -1,12 +0,0 @@
Tinytest.add("cookies - parse and stringify", function (test) {
test.equal(Cookies.parse("a=b; c=d"), {a: "b", c: "d"});
test.equal(Cookies.parse("a=b;c=d"), {a: "b", c: "d"});
test.equal(Cookies.parse("a=b;c=d;e=12"), {a: "b", c: "d", e: "12"});
test.equal(Cookies.parse("a=b=c;d=e=f"), {a: "b=c", d: "e=f"});
// results depends on object key order being preserved, but it will
// probably work in all of our test hosts
test.equal(Cookies.stringify({a: "1"}), "a=1");
test.equal(Cookies.stringify({a: "1", b: "2"}), "a=1;b=2");
test.equal(Cookies.stringify({a: "a=1", b: "b=2"}), "a=a=1;b=b=2");
});

View File

@@ -1,20 +0,0 @@
Package.describe({
summary: "Parsing cookies",
internal: true
});
Package.on_use(function (api) {
api.use('underscore', ['client', 'server']);
api.export('Cookies', ['client', 'server']);
api.add_files('cookies.js', ['client', 'server']);
api.add_files('cookies_client.js', ['client']);
});
Package.on_test(function (api) {
api.use('cookies', ['client', 'server']);
api.use('tinytest', ['client', 'server']);
api.use('random', ['client']);
api.add_files('cookies_test.js', ['client', 'server']);
api.add_files('cookies_client_test.js', ['client']);
});

View File

@@ -1,5 +1,5 @@
Package.describe({
summary: "Require this application to use secure transport (HTTPS)"
summary: "Require this application to use HTTPS"
});
Package.on_use(function (api) {

View File

@@ -1,5 +1,5 @@
Package.describe({
summary: "Execute a function when the user scrolls past an element"
summary: "Run a function when the user scrolls past an element"
});
Package.on_use(function (api) {

View File

@@ -0,0 +1,2 @@
#id {}

View File

@@ -14,6 +14,6 @@ Package._transitional_registerBuildPlugin({
Package.on_test(function (api) {
api.use(['test-helpers', 'tinytest', 'less']);
api.use(['spark']);
api.add_files(['less_tests.less', 'less_tests.js', 'less_tests.import.less'],
'client');
api.add_files(['less_tests.less', 'less_tests.js', 'less_tests.import.less',
'less_tests_empty.less'], 'client');
});

View File

@@ -42,23 +42,25 @@ Plugin.registerSourceHandler("less", function (compileStep) {
return;
}
var cssFuture = new Future;
var sourceMap = null;
var css = ast.toCSS({
sourceMap: Boolean(true),
writeSourceMap: function (sourceMap) {
cssFuture.return(sourceMap);
sourceMap: true,
writeSourceMap: function (sm) {
sourceMap = JSON.parse(sm);
}
});
var sourceMap = JSON.parse(cssFuture.wait());
sourceMap.sources = [compileStep.inputPath];
sourceMap.sourcesContent = [source];
if (sourceMap) {
sourceMap.sources = [compileStep.inputPath];
sourceMap.sourcesContent = [source];
sourceMap = JSON.stringify(sourceMap);
}
compileStep.addStylesheet({
path: compileStep.inputPath + ".css",
data: css,
sourceMap: JSON.stringify(sourceMap)
sourceMap: sourceMap
});
});;

View File

@@ -1,5 +1,6 @@
Package.describe({
summary: "Meteor developer accounts OAuth flow"
summary: "Meteor developer accounts OAuth flow",
internal: true
});
Package.on_use(function (api) {

View File

@@ -1,5 +1,5 @@
Package.describe({
summary: "Automatically preserve all form fields with a unique id"
summary: "Automatically preserve form fields with a unique id"
});
Package.on_use(function (api) {

View File

@@ -1,5 +1,5 @@
Package.describe({
summary: "Collection of small helper functions: _.map, _.each, ..."
summary: "Collection of small helpers: _.map, _.each, ..."
});
Package.on_use(function (api) {

View File

@@ -247,19 +247,13 @@ var runWebAppServer = function () {
// webserver
var app = connect();
// Parse the query string into res.query. Used by oauth_server, but it's
// generally pretty handy..
app.use(connect.query());
// Auto-compress any json, javascript, or text.
app.use(connect.compress());
// Packages and apps can add handlers to this via
// WebApp.connectHandlers. They are inserted before our default
// handler. If a path prefix is in use, they see the actual
// requested URL before the path prefix has been stripped off.
var packageAndAppHandlers = connect();
app.use(packageAndAppHandlers);
// Packages and apps can add handlers that run before any other Meteor
// handlers via WebApp.rawConnectHandlers.
var rawConnectHandlers = connect();
app.use(rawConnectHandlers);
// Strip off the path prefix, if it exists.
app.use(function (request, response, next) {
@@ -284,6 +278,10 @@ var runWebAppServer = function () {
}
});
// Parse the query string into res.query. Used by oauth_server, but it's
// generally pretty handy..
app.use(connect.query());
var getItemPathname = function (itemUrl) {
return decodeURIComponent(url.parse(itemUrl).pathname);
};
@@ -415,6 +413,11 @@ var runWebAppServer = function () {
.pipe(res);
});
// Packages and apps can add handlers to this via WebApp.connectHandlers.
// They are inserted before our default handler.
var packageAndAppHandlers = connect();
app.use(packageAndAppHandlers);
var suppressConnectErrors = false;
// connect knows it is an error handler because it has 4 arguments instead of
// 3. go figure. (It is not smart enough to find such a thing if it's hidden
@@ -520,6 +523,7 @@ var runWebAppServer = function () {
// start up app
_.extend(WebApp, {
connectHandlers: packageAndAppHandlers,
rawConnectHandlers: rawConnectHandlers,
httpServer: httpServer,
// metadata about the client program that we serve
clientProgram: {

View File

@@ -1,4 +1,7 @@
=> Meteor 0.7.0.1: Fix failure to initialize local MongoDB server.
=> Meteor 0.7.1.1: Extend oplog tailing driver to support most common
MongoDB queries. Introduce Meteor developer accounts, a new way of
managing your meteor.com deployed sites. When you use `meteor
deploy`, you will be prompted to create a developer account.
This release is being downloaded in the background. Update your
project to Meteor 0.7.0.1 by running 'meteor update'.
project to Meteor 0.7.1.1 by running 'meteor update'.

View File

@@ -78,6 +78,16 @@
{
"release": "0.7.0.1"
},
{
"release": "0.7.1"
},
{
"release": "0.7.1.1",
"packageNotices": {
"jquery": ["jquery has been upgraded to 1.11.0. See ",
"http://jquery.com/upgrade-guide/1.9/"]
}
},
{
"release": "NEXT"
}

View File

@@ -50,25 +50,41 @@ var withAccountsConnection = function (f) {
};
};
// Open a DDP connection to the accounts server, log in using the
// provided token, and ensure that the connection stays logged in across
// reconnects.
// Open a DDP connection to the accounts server and log in using the
// provided token. Returns the connection, or null if login fails.
//
// XXX if we reconnect we won't reauthenticate. Fix that before using
// this for long-lived connections.
var loggedInAccountsConnection = function (token) {
var connection = getLoadedPackages().livedata.DDP.connect(
config.getAuthDDPUrl()
);
var onReconnect = function () {
connection.apply(
'login',
[{ resume: token }],
{ wait: true },
function (err, result) {
if (err)
throw err;
}
);
};
onReconnect();
var fut = new Future;
connection.apply(
'login',
[{ resume: token }],
{ wait: true },
function (err, result) {
fut['return']({ err: err, result: result });
}
);
var outcome = fut.wait();
if (outcome.err) {
connection.close();
if (outcome.err.error === 403) {
// This is not an ideal value for the error code, but it means
// "server rejected our access token". For example, it expired
// or we revoked it from the web.
return null;
}
// Something else went wrong
throw outcome.err;
}
return connection;
};
@@ -189,26 +205,33 @@ var writeMeteorAccountsUsername = function (username) {
// Given an object 'data' in the format returned by readSessionData,
// modify it to make the user logged out.
var logOutAllSessions = function (data) {
_.each(data.sessions, function (session, domain) {
logOutSession(session);
});
};
// As logOutAllSessions, but for a session on a particular domain
// rather than all sessions.
var logOutSession = function (session) {
var crypto = require('crypto');
_.each(data.sessions, function (session, domain) {
delete session.username;
delete session.userId;
delete session.username;
delete session.userId;
delete session.registrationUrl;
if (_.has(session, 'token')) {
if (! (session.pendingRevoke instanceof Array))
session.pendingRevoke = [];
if (_.has(session, 'token')) {
if (! (session.pendingRevoke instanceof Array))
session.pendingRevoke = [];
// Delete the auth token itself, but save the tokenId, which
// is useless for authentication. The next time we're online,
// we'll send the tokenId to the server to revoke the token on
// the server side too.
if (typeof session.tokenId === "string")
session.pendingRevoke.push(session.tokenId);
delete session.token;
delete session.tokenId;
}
});
// Delete the auth token itself, but save the tokenId, which is
// useless for authentication. The next time we're online, we'll
// send the tokenId to the server to revoke the token on the
// server side too.
if (typeof session.tokenId === "string")
session.pendingRevoke.push(session.tokenId);
delete session.token;
delete session.tokenId;
}
};
// Given an object 'data' in the format returned by readSessionData,
@@ -222,37 +245,7 @@ var loggedIn = function (data) {
// the logged in user doesn't have a username.
var currentUsername = function (data) {
var sessionData = getSession(data, config.getAccountsDomain());
if (sessionData.username) {
return sessionData.username;
} else if (loggedIn(data) && sessionData.token) {
// If it looks like we are logged in but we don't yet have a
// username, then ask the server if we have one.
var username = null;
var fut = new Future();
var connection = loggedInAccountsConnection(sessionData.token);
connection.call('getUsername', function (err, result) {
if (err) {
// If anything went wrong, return null just as we would have if
// we hadn't bothered to ask the server.
fut['return'](null);
return;
}
fut['return'](result);
});
setTimeout(inFiber(function () {
fut['return'](null);
}), 5000);
username = fut.wait();
connection.close();
if (username) {
writeMeteorAccountsUsername(username);
}
return username;
} else {
return null;
}
return sessionData.username || null;
};
var removePendingRevoke = function (domain, tokenIds) {
@@ -584,13 +577,18 @@ var doInteractivePasswordLogin = function (options) {
return true;
};
// options are the same as for doInteractivePasswordLogin, exept without
// options are the same as for doInteractivePasswordLogin, except without
// username and email.
exports.doUsernamePasswordLogin = function (options) {
var username = utils.readLine({
prompt: "Username: ",
stream: process.stderr
});
var username;
do {
username = utils.readLine({
prompt: "Username: ",
stream: process.stderr
}).trim();
} while (username.length === 0);
return doInteractivePasswordLogin(_.extend({}, options, {
username: username
}));
@@ -685,9 +683,71 @@ exports.logoutCommand = function (options) {
process.stderr.write("Not logged in.\n");
};
exports.currentUsername = function () {
// If this is fully set up account (with a username and password), or
// if not logged in, do nothing. If it is an account without a
// username, contact the server and see if the user finished setting
// it up, and if so grab and save the username. But contact the server
// only once per run of the program. Options:
// - noLogout: boolean. Set to true if you don't want this function to
// log out the session if wehave an invalid credential (for example,
// if a caller wants to do its own error handling for invalid
// credentials). Defaults to false.
var alreadyPolledForRegistration = false;
exports.pollForRegistrationCompletion = function (options) {
if (alreadyPolledForRegistration)
return;
alreadyPolledForRegistration = true;
options = options || {};
var data = readSessionData();
return currentUsername(data);
var session = getSession(data, config.getAccountsDomain());
if (session.username || ! session.token)
return;
// We are logged in but we don't yet have a username. Ask the server
// if a username was chosen since we last checked.
var username = null;
var fut = new Future();
var connection = loggedInAccountsConnection(session.token);
if (! connection) {
// Server says our credential isn't any good anymore! Get rid of
// it. Note that, out of an abundance of caution, this also will
// enqueue the credential for invalidation (on a future run, we
// will try to explicitly revoke the credential ourselves).
if (! options.noLogout) {
logOutSession(session);
writeSessionData(data);
}
return;
}
connection.call('getUsername', function (err, result) {
if (fut.isResolved())
return;
if (err) {
// If anything went wrong, return null just as we would have if
// we hadn't bothered to ask the server.
fut['return'](null);
return;
}
fut['return'](result);
});
var timer = setTimeout(inFiber(function () {
if (! fut.isResolved()) {
fut['return'](null);
}
}), 5000);
username = fut.wait();
connection.close();
clearTimeout(timer);
if (username) {
writeMeteorAccountsUsername(username);
}
};
exports.registrationUrl = function () {
@@ -698,6 +758,7 @@ exports.registrationUrl = function () {
exports.whoAmICommand = function (options) {
config.printUniverseBanner();
auth.pollForRegistrationCompletion();
var data = readSessionData();
if (! loggedIn(data)) {
@@ -775,11 +836,23 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
} else if (result.alreadyExisted && result.sentRegistrationEmail) {
process.stderr.write(
"\n" +
"That email address is already in use. We need to confirm that it belongs\n" +
"to you. Luckily this will only take a moment.\n" +
"\n" +
"Check your mail! We've sent you a link. Click it, pick a password,\n" +
"and then come back here to deploy your app.\n");
"You need to pick a password for your account so that you can log in.\n" +
"An email has been sent to you with the link.\n\n");
var animationFrame = 0;
var lastLinePrinted = "";
var timer = setInterval(function () {
var spinner = ['-', '\\', '|', '/'];
lastLinePrinted = "Waiting for you to register on the web... " +
spinner[animationFrame];
process.stderr.write(lastLinePrinted + "\r");
animationFrame = (animationFrame + 1) % spinner.length;
}, 200);
var stopSpinner = function () {
process.stderr.write(new Array(lastLinePrinted.length + 1).join(' ') +
"\r");
clearInterval(timer);
};
try {
var waitForRegistrationResult = connection.call(
@@ -787,17 +860,17 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
email
);
} catch (e) {
stopSpinner();
if (! (e instanceof getLoadedPackages().meteor.Meteor.Error))
throw e;
process.stderr.write(
"\nWhen you've picked your password, run 'meteor login' and then you'll\n" +
"be good to go.\n");
"When you've picked your password, run 'meteor login' to log in.\n")
return false;
}
process.stderr.write("\nGreat! Nice to meet you, " +
waitForRegistrationResult.username +
"! Now log in with your new password.\n");
stopSpinner();
process.stderr.write("Username: " +
waitForRegistrationResult.username + "\n");
loginResult = doInteractivePasswordLogin({
username: waitForRegistrationResult.username,
retry: true,
@@ -821,6 +894,34 @@ exports.registerOrLogIn = withAccountsConnection(function (connection) {
}
});
// options: firstTime, leadingNewline
exports.maybePrintRegistrationLink = function (options) {
options = options || {};
auth.pollForRegistrationCompletion();
var data = readSessionData();
var session = getSession(data, config.getAccountsDomain());
if (session.userId && ! session.username && session.registrationUrl) {
if (options.leadingNewline)
process.stderr.write("\n");
if (! options.firstTime) {
// If they've already been prompted to set a password then this
// is more of a friendly reminder, so we word it slightly
// differently than the first time they're being shown a
// registration url.
process.stderr.write(
"You should set a password on your Meteor developer account. It takes\n" +
"about a minute at: " + session.registrationUrl + "\n\n");
} else {
process.stderr.write(
"You can set a password on your account or change your email address at:\n" +
session.registrationUrl + "\n\n");
}
}
};
exports.tryRevokeOldTokens = tryRevokeOldTokens;
exports.getSessionId = function (domain) {

View File

@@ -840,8 +840,16 @@ _.extend(ClientTarget.prototype, {
_.each(originals, function (file, name) {
if (! file.sourceMap)
return;
newMap.applySourceMap(
new sourcemap.SourceMapConsumer(file.sourceMap), name);
try {
newMap.applySourceMap(
new sourcemap.SourceMapConsumer(file.sourceMap), name);
} catch (err) {
// If we can't apply the source map, silently drop it.
//
// XXX This is here because there are some less files that
// produce source maps that throw when consumed. We should
// figure out exactly why and fix it, but this will do for now.
}
});
self.css[0].setSourceMap(JSON.stringify(newMap));

View File

@@ -678,6 +678,7 @@ main.registerCommand({
}
}, function (options) {
var mongoUrl;
var usedMeteorAccount = false;
if (options.args.length === 0) {
// localhost mode
@@ -708,6 +709,7 @@ main.registerCommand({
mongoUrl = deployGalaxy.temporaryMongoUrl(site);
} else {
mongoUrl = deploy.temporaryMongoUrl(site);
usedMeteorAccount = true;
}
if (! mongoUrl)
@@ -717,6 +719,8 @@ main.registerCommand({
if (options.url) {
console.log(mongoUrl);
} else {
if (usedMeteorAccount)
auth.maybePrintRegistrationLink();
process.stdin.pause();
var runMongo = require('./run-mongo.js');
runMongo.runMongoShell(mongoUrl);
@@ -773,7 +777,7 @@ main.registerCommand({
main.registerCommand({
name: 'deploy',
minArgs: 0,
minArgs: 1,
maxArgs: 1,
options: {
'delete': { type: Boolean, short: 'D' },
@@ -862,26 +866,16 @@ main.registerCommand({
});
}
var registrationUrl = auth.registrationUrl();
if (registrationUrl &&
deployResult === 0 &&
! auth.currentUsername()) {
process.stderr.write("\n");
if (loggedIn) {
if (deployResult === 0) {
auth.maybePrintRegistrationLink({
leadingNewline: true,
// If the user was already logged in at the beginning of the
// deploy, then they've already been prompted to set a password
// and this is more of a friendly reminder to set their password,
// so we word it slightly differently than the first time they're
// being shown a registration url.
process.stderr.write(
"You should set a password on your Meteor developer account. It takes\n" +
"about a minute at: " + registrationUrl + "\n\n");
} else {
process.stderr.write(
"You can set a password on your account or change your email address at:\n" +
registrationUrl + "\n\n");
}
// at least once before, so we use a slightly different message.
firstTime: ! loggedIn
});
}
return deployResult;
});
@@ -942,6 +936,7 @@ main.registerCommand({
}
config.printUniverseBanner();
auth.pollForRegistrationCompletion();
var site = qualifySitename(options.args[0]);
if (hostedWithGalaxy(site)) {
@@ -976,15 +971,14 @@ main.registerCommand({
maxArgs: 1
}, function (options) {
config.printUniverseBanner();
auth.pollForRegistrationCompletion();
var site = qualifySitename(options.args[0]);
if (! auth.isLoggedIn()) {
// XXX meteor.com/create-account or something should have a nice
// registration form
process.stderr.write(
"\nYou must be logged in to claim sites. Use 'meteor login' to log in.\n" +
"If you don't have a Meteor developer account yet, you can quickly\n" +
"create one at www.meteor.com.\n\n");
"You must be logged in to claim sites. Use 'meteor login' to log in.\n" +
"If you don't have a Meteor developer account yet, create one by clicking\n" +
"'Sign in' and then 'Create account' at www.meteor.com.\n\n");
return 1;
}
@@ -1202,10 +1196,14 @@ main.registerCommand({
name: 'login',
options: {
email: { type: String },
// Undocumented: get credentials on a specific Galaxy. Do we still
// need this?
galaxy: { type: String }
}
}, function (options) {
return auth.loginCommand(options);
return auth.loginCommand(_.extend({
overwriteExistingToken: true
}, options));
});
@@ -1252,12 +1250,13 @@ main.registerCommand({
main.registerCommand({
name: 'self-test',
minArgs: 0,
maxArgs: 1,
options: {
changed: { type: Boolean },
'force-online': { type: Boolean },
slow: { type: Boolean },
history: { type: Number },
tests: { type: String }
},
hidden: true
}, function (options) {
@@ -1275,13 +1274,13 @@ main.registerCommand({
}
var testRegexp = undefined;
if (options.tests) {
if (options.args.length) {
try {
testRegexp = new RegExp(options.tests);
testRegexp = new RegExp(options.args[0]);
} catch (e) {
if (!(e instanceof SyntaxError))
throw e;
process.stderr.write("Bad regular expression: " + options.tests + "\n");
process.stderr.write("Bad regular expression: " + options.args[0] + "\n");
return 1;
}
}

View File

@@ -11,6 +11,7 @@ var auth = require('./auth.js');
var release = require('./release.js');
var url = require('url');
var _ = require('underscore');
var buildmessage = require('./buildmessage.js');
// a bit of a hack
var getPackage = _.once(function () {
@@ -32,14 +33,14 @@ var handleError = function (error, galaxyName, messages) {
var Package = getPackage();
messages = messages || {};
if (e instanceof Package.meteor.Meteor.Error) {
if (error instanceof Package.meteor.Meteor.Error) {
var msg = messages[error.error];
if (msg)
process.stderr.write(msg + "\n");
else if (error.message)
process.stderr.write("Denied: " + error.message + "\n");
return 1;
} else if (e instanceof ConnectionTimeoutError) {
} else if (error instanceof ConnectionTimeoutError) {
// If we have an http/https URL for a galaxyName instead of a
// proper galaxyName (which is what the code in this file
// currently passes), strip off the scheme and trailing slash.
@@ -150,7 +151,7 @@ _.extend(ServiceConnection.prototype, {
}
});
self.subscribe.apply(self, args);
self.connection.subscribe.apply(self.connection, args);
return fut.wait();
},
@@ -414,7 +415,7 @@ exports.deploy = function (options) {
return 0;
} finally {
// Close the connection to Galaxy (otherwise Node will continue running).
conn.close();
conn && conn.close();
}
};

View File

@@ -147,10 +147,13 @@ var authedRpc = function (options) {
expectPayload: []
});
if (infoResult.statusCode === 403 && rpcOptions.promptIfAuthFails) {
if (infoResult.statusCode === 401 && rpcOptions.promptIfAuthFails) {
// Our authentication didn't validate, so prompt the user to log in
// again, and resend the RPC if the login succeeds.
var username = utils.readLine({ prompt: "Username: " });
var username = utils.readLine({
prompt: "Username: ",
stream: process.stderr
});
var loginOptions = {
username: username,
suppressErrorMessage: true
@@ -262,9 +265,9 @@ var printLegacyPasswordMessage = function (site) {
// authorized for, instruct them to get added via 'meteor authorized
// --add' or switch accounts.
var printUnauthorizedMessage = function () {
var username = auth.currentUsername();
var username = auth.loggedInUsername();
process.stderr.write(
"\nSorry, that site belongs to a different user.\n" +
"Sorry, that site belongs to a different user.\n" +
(username ? "You are currently logged in as " + username + ".\n" : "") +
"\nEither have the site owner use 'meteor authorized --add' to add you\n" +
"as an authorized developer for the site, or switch to an authorized\n" +
@@ -313,12 +316,27 @@ var bundleAndDeploy = function (options) {
if (! site)
return 1;
// We should give a username/password prompt if the user was logged in
// but the credentials are expired, unless the user is logged in but
// doesn't have a username (in which case they should hit the email
// prompt -- a user without a username shouldn't be given a username
// prompt). There's an edge case where things happen in the following
// order: user creates account, user sets username, credential expires
// or is revoked, user comes back to deploy again. In that case,
// 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({
noLogout: true
});
var promptIfAuthFails = (auth.loggedInUsername() !== null);
// Check auth up front, rather than after the (potentially lengthy)
// bundling process.
var preflight = authedRpc({
site: site,
preflight: true,
promptIfAuthFails: true
promptIfAuthFails: promptIfAuthFails
});
if (preflight.errorMessage) {
@@ -341,7 +359,7 @@ var bundleAndDeploy = function (options) {
var buildDir = path.join(options.appDir, '.meteor', 'local', 'build_tar');
var bundlePath = path.join(buildDir, 'bundle');
process.stdout.write('Deploying to ' + site + '. Bundling...\n');
process.stdout.write('Deploying to ' + site + '. Bundling...\n');
var settings = null;
var messages = buildmessage.capture({
@@ -487,6 +505,7 @@ var checkAuthThenSendRpc = function (site, operation, what) {
return null;
}
} else { // User is logged in but not authorized for this app
process.stderr.write("\n");
printUnauthorizedMessage();
return null;
}
@@ -539,6 +558,7 @@ var logs = function (site) {
return 1;
} else {
process.stdout.write(result.message);
auth.maybePrintRegistrationLink({ leadingNewline: true });
return 0;
}
};
@@ -659,11 +679,12 @@ var claim = function (site) {
});
if (result.errorMessage) {
if (! auth.currentUsername() &&
auth.pollForRegistrationCompletion();
if (! auth.loggedInUsername() &&
auth.registrationUrl()) {
process.stderr.write(
"\nBefore you can claim existing sites, you need to set a password on\n" +
"your Meteor developer account. You can do that here in under a minute:\n\n" +
"You need to set a password on your Meteor developer account before\n" +
"you can claim sites. You can do that here in under a minute:\n\n" +
auth.registrationUrl() + "\n\n");
} else {
process.stderr.write("Couldn't claim site: " +

View File

@@ -144,8 +144,7 @@ Options:
Reset the project state. Erases the local database.
Usage: meteor reset
Reset the current project to a fresh state. Removes all local
data.
Reset the current project to a fresh state. Removes all local data.
>>> deploy
@@ -218,6 +217,32 @@ 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]
Prompts for your username and password and logs you in to your Meteor
developer account. Pass --email to log in by email address rather than
by username.
>>> logout
Log out of your Meteor developer account
Usage: meteor logout
Log out of your Meteor developer account.
>>> whoami
Prints the username of your Meteor developer account
Usage: meteor whoami
Prints the username of the currently logged-in Meteor developer.
See 'meteor login' to log into or 'meteor logout' to log out of your
Meteor developer account.
>>> test-packages
Test one or more packages
Usage: meteor test-packages [--release <release>] [options] [package...]
@@ -291,30 +316,31 @@ Prompts for your username and password and logs you in to your
Meteor account. Pass --email to log in by email address instead of
username. Pass --galaxy to specify a galaxy to log in to.
Builds the provided directory as a package, then loads the package and
calls the main() function inside the package. The function will receive
any remaining arguments. The exit status will be the return value of
main() (which is called inside a fiber).
>>> logout
Log out of your Meteor account
Usage: meteor logout
The arguments will be parsed by Meteor's option parser, which means that
--release will be effective (but not passed to the command), and that it will be
an error to pass any unknown options. If you want to pass options to your tool,
place them after a '--' argument (which turns off option parsing for the rest of
the arguments).
Log out of your Meteor account.
>>> whoami
Prints the username of your Meteor developer account
Usage: meteor whoami
Prints the username of the currently logged-in Meteor developer.
See 'meteor login' to log into or 'meteor logout' to log out of of your
Meteor account.
This command is for temporary, internal use, until we have a more mature
system for building standalone command-line programs with Meteor.
>>> self-test
Run tests of the 'meteor' tool.
Usage: meteor self-test [--changed] [--slow] [--force-online] [--history n]
Usage: meteor self-test [pattern] [--changed] [--slow]
[--force-online] [--history n]
Runs internal tests. Exits with status 0 on success.
If 'pattern' is provided, it should be a regular expression. Only
tests that match the regular expression will be run.
Pass --changed to run only tests that have changed since they last
passed. This uses a really rough heuristic: A test has changed iff
there has been any change to the file in the 'selftests' subdirectory
@@ -361,4 +387,3 @@ Grant a permission on an official service
Usage: meteor admin grant [XXX]
Not yet implemented

View File

@@ -573,7 +573,19 @@ Fiber(function () {
// appRelease will be null if a super old project with no
// .meteor/release or 'none' if created by a checkout
appRelease = project.getMeteorReleaseVersion(appDir);
// This is what happens if the file exists and is empty. This really
// shouldn't happen unless the user did it manually.
if (appRelease === '') {
process.stderr.write(
"Problem! This project has a .meteor/release file which is empty.\n" +
"The file should either contain the release of Meteor that you want to use,\n" +
"or the word 'none' if you will only use the project with unreleased\n" +
"checkouts of Meteor. Please edit the .meteor/release file in the project\n" +
"and change it to a valid Meteor release or 'none'.\n");
process.exit(1);
}
}
if (! files.usesWarehouse()) {
// Running from a checkout
if (releaseOverride) {
@@ -909,7 +921,7 @@ commandName + ": You're not in a Meteor project directory.\n" +
}
if (command.requiresApp && release.current.isCheckout() &&
appRelease !== "none") {
appRelease && appRelease !== "none") {
// For commands that work with apps, if we have overridden the
// app's usual release by using a checkout, print a reminder banner.
process.stderr.write(

View File

@@ -336,7 +336,8 @@ _.extend(Slice.prototype, {
// - appendDocument({ section: "head", data: "my markup" })
// Browser targets only. Add markup to the "head" or "body"
// section of the document.
// - addStylesheet({ path: "my/stylesheet.css", data: "my css" })
// - addStylesheet({ path: "my/stylesheet.css", data: "my css",
// sourceMap: "stringified json sourcemap"})
// Browser targets only. Add a stylesheet to the
// document. 'path' is a requested URL for the stylesheet that
// may or may not ultimately be honored. (Meteor will add
@@ -453,7 +454,8 @@ _.extend(Slice.prototype, {
resources.push({
type: "css",
data: new Buffer(options.data, 'utf8'),
servePath: path.join(self.pkg.serveRoot, options.path)
servePath: path.join(self.pkg.serveRoot, options.path),
sourceMap: options.sourceMap
});
},
addJavaScript: function (options) {

View File

@@ -57,7 +57,8 @@ var meteorReleaseFilePath = function (appDir) {
// This will return "none" if the project is not pinned to a release
// (it was created by a checkout), or null for a legacy app with no
// .meteor/release file.
// .meteor/release file. It returns the empty string if the file exists
// but is empty.
project.getMeteorReleaseVersion = function (appDir) {
var releasePath = meteorReleaseFilePath(appDir);
try {
@@ -65,6 +66,9 @@ project.getMeteorReleaseVersion = function (appDir) {
} catch (e) {
return null;
}
// This should really never happen, and the caller will print a special error.
if (!lines.length)
return '';
return trimLine(lines[0]);
};

View File

@@ -477,6 +477,21 @@ _.extend(Sandbox.prototype, {
unlink: function (filename) {
var self = this;
fs.unlinkSync(path.join(self.cwd, filename));
},
// Return the current contents of .meteorsession in the sandbox.
readSessionFile: function () {
var self = this;
return fs.readFileSync(path.join(self.root, '.meteorsession'), 'utf8');
},
// Overwrite .meteorsession in the sandbox with 'contents'. You
// could use this in conjunction with readSessionFile to save and
// restore authentication states.
writeSessionFile: function (contents) {
var self = this;
return fs.writeFileSync(path.join(self.root, '.meteorsession'),
contents, 'utf8');
}
});
@@ -839,7 +854,7 @@ _.extend(Run.prototype, {
var net = require('net');
var lastStartTime = 0;
for (var attempts = 0; ! self.fakeMongoConnection && attempts < 20;
for (var attempts = 0; ! self.fakeMongoConnection && attempts < 50;
attempts ++) {
// Throttle attempts to one every 100ms
utils.sleepMs((lastStartTime + 100) - (+ new Date));
@@ -976,7 +991,7 @@ var tagDescriptions = {
// these last two are not actually test tags; they reflect the use of
// --changed and --tests
unchanged: 'unchanged since last pass',
misnamed: "don't match --tests argument"
'non-matching': "don't match specified pattern"
};
// options: onlyChanged, offline, includeSlowTests, historyLines, testRegexp
@@ -1015,7 +1030,7 @@ var runTests = function (options) {
tests = _.filter(tests, function (test) {
return options.testRegexp.test(test.name);
});
skipCounts.misnamed = lengthBeforeTestRegexp - tests.length;
skipCounts['non-matching'] = lengthBeforeTestRegexp - tests.length;
}
if (options.onlyChanged) {

View File

@@ -1,3 +1,8 @@
var _ = require('underscore');
var release = require('./release.js');
var unipackage = require('./unipackage.js');
var config = require('./config.js');
var randomString = function (charsCount) {
var chars = 'abcdefghijklmnopqrstuvwxyz';
var str = '';
@@ -7,6 +12,8 @@ var randomString = function (charsCount) {
return str;
};
exports.accountsCommandTimeoutSecs = 15;
exports.randomString = randomString;
var randomAppName = function () {
@@ -63,12 +70,22 @@ exports.cleanUpLegacyApp = function (sandbox, name, password) {
};
// Creates an app and deploys it. Assumes the sandbox is already logged
// in.
exports.createAndDeployApp = function (sandbox) {
var name = randomAppName();
sandbox.createApp(name, 'empty');
// in. Returns the name of the deployed app. Options:
// - settingsFile: a path to a settings file to deploy with
// - appName: app name to use; will be generated randomly if not
// provided
// - templateApp: the name of the template app to use. defaults to 'empty'
exports.createAndDeployApp = function (sandbox, options) {
options = options || {};
var name = options.appName || randomAppName();
sandbox.createApp(name, options.templateApp || 'empty');
sandbox.cd(name);
var run = sandbox.run('deploy', name);
var runArgs = ['deploy', name];
if (options.settingsFile) {
runArgs.push('--settings');
runArgs.push(options.settingsFile);
}
var run = sandbox.run.apply(sandbox, runArgs);
run.waitSecs(90);
run.match('Now serving at ' + name + '.meteor.com');
run.waitSecs(10);
@@ -86,7 +103,7 @@ exports.cleanUpApp = function (sandbox, name) {
exports.login = function (s, username, password) {
var run = s.run('login');
run.waitSecs(2);
run.waitSecs(15);
run.matchErr('Username:');
run.write(username + '\n');
run.matchErr('Password:');
@@ -102,3 +119,67 @@ exports.logout = function (s) {
run.matchErr('Logged out');
run.expectExit(0);
};
var registrationUrlRegexp =
/https:\/\/www\.meteor\.com\/setPassword\?([a-zA-Z0-9\+\/]+)/;
exports.registrationUrlRegexp = registrationUrlRegexp;
// In the sandbox `s`, create and deploy a new app with an unregistered
// email address. Returns the registration token from the printed URL in
// the deploy message.
exports.deployWithNewEmail = function (s, email, appName) {
s.createApp('deployapp', 'empty');
s.cd('deployapp');
var run = s.run('deploy', appName);
run.waitSecs(exports.accountsCommandTimeoutSecs);
run.matchErr('Email:');
run.write(email + '\n');
run.waitSecs(90);
// Check that we got a prompt to set a password on meteor.com.
run.matchErr('set a password');
var urlMatch = run.matchErr(registrationUrlRegexp);
if (! urlMatch || ! urlMatch.length || ! urlMatch[1]) {
throw new Error("Missing registration token");
}
var token = urlMatch[1];
run.expectExit(0);
return token;
};
var getLoadedPackages = _.once(function () {
return unipackage.load({
library: release.current.library,
packages: ['meteor', 'livedata'],
release: release.current.name
});
});
var ddpConnect = function (url) {
var DDP = getLoadedPackages().livedata.DDP;
return DDP.connect(url);
};
exports.ddpConnect = ddpConnect;
// Given a registration token created by doing a deferred registration
// with `email`, makes a DDP connection to the accounts server and
// finishes the registration process.
exports.registerWithToken = function (token, username, password, email) {
// XXX It might make more sense to hard-code the DDP url to
// https://www.meteor.com, since that's who the sandboxes are talking
// to.
var accountsConn = ddpConnect(config.getAuthDDPUrl());
var registrationTokenInfo = accountsConn.call('registrationTokenInfo',
token);
var registrationCode = registrationTokenInfo.code;
accountsConn.call('register', {
username: username,
password: password,
emails: [email],
token: token,
code: registrationCode
});
accountsConn.close();
};

View File

@@ -2,7 +2,7 @@ var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var Sandbox = selftest.Sandbox;
var commandTimeoutSecs = 15;
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
var loggedInError = function(run) {
run.waitSecs(commandTimeoutSecs);
@@ -24,6 +24,9 @@ selftest.define("authorized", ['net', 'slow'], function () {
run.matchErr("You must be logged in for that.");
run.expectExit(1);
run = s.run("authorized");
run.matchErr("not enough arguments");
run = s.run("authorized", appName, "--remove", "bob");
run.waitSecs(commandTimeoutSecs);
run.matchErr("You must be logged in for that.");
@@ -116,3 +119,26 @@ selftest.define("authorized", ['net', 'slow'], function () {
testUtils.cleanUpApp(s, appName);
});
selftest.define('authorized - no username', ['net', 'slow'], function () {
var s = new Sandbox;
// We shouldn't be able to add authorized users before we set a
// username.
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
var run = s.run('authorized', appName, '--add', 'test');
run.waitSecs(commandTimeoutSecs);
run.matchErr('You must set a password on your account before ' +
'you can authorize other users');
run.expectExit(1);
// After we set a username, we should be able to authorize others.
testUtils.registerWithToken(token, username, 'testtest', email);
run = s.run('authorized', appName, '--add', 'test');
run.waitSecs(commandTimeoutSecs);
run.match(': added test');
run.expectExit(0);
testUtils.cleanUpApp(s, appName);
});

View File

@@ -1,8 +1,9 @@
var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var Sandbox = selftest.Sandbox;
var files = require('../files.js');
var commandTimeoutSecs = 15;
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
var loggedInError = selftest.markStack(function(run) {
run.waitSecs(commandTimeoutSecs);
@@ -24,6 +25,11 @@ selftest.define("claim", ['net', 'slow'], function () {
var run = s.run('claim', testUtils.randomAppName(20));
loggedInError(run);
// Can't claim sites without specifying a site
run = s.run('claim');
run.matchErr('not enough arguments');
run.expectExit(1);
// Existing site.
run = s.run('claim', 'mother-test');
loggedInError(run);
@@ -46,6 +52,7 @@ selftest.define("claim", ['net', 'slow'], function () {
testUtils.login(s, "test", "testtest");
run = s.run('authorized', appName, '--add', 'testtest');
run.waitSecs(commandTimeoutSecs);
run.match('added');
run.expectExit(0);
testUtils.logout(s);
testUtils.login(s, "testtest", "testtest");
@@ -55,13 +62,18 @@ selftest.define("claim", ['net', 'slow'], function () {
testUtils.cleanUpApp(s, appName);
// Legacy sites.
var sLegacy = new Sandbox({
// Include a warehouse argument so that we can deploy apps with
// --release arguments.
warehouse: {
v1: { tools: 'tool1', latest: true }
}
});
var sLegacy;
if (files.inCheckout()) {
sLegacy = new Sandbox({
// Include a warehouse argument so that we can deploy apps with
// --release arguments.
warehouse: {
v1: { tools: 'tool1', latest: true }
}
});
} else {
sLegacy = new Sandbox;
}
// legacy w/pwd.
var pwd = testUtils.randomString(10);
@@ -99,3 +111,48 @@ selftest.define("claim", ['net', 'slow'], function () {
testUtils.cleanUpApp(s, legacyApp);
});
selftest.define('claim - no username', ['net', 'slow'], function () {
var s = new Sandbox;
var sandboxWithWarehouse;
if (files.inCheckout()) {
sandboxWithWarehouse = new Sandbox({
// Include a warehouse argument so that we can deploy apps with
// --release arguments.
warehouse: {
v1: { tools: 'tool1', latest: true }
}
});
} else {
sandboxWithWarehouse = new Sandbox;
}
// We shouldn't be able to claim sites before we set a username.
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
var legacyAppName = testUtils.createAndDeployLegacyApp(
sandboxWithWarehouse,
'test'
);
var run = s.run('claim', legacyAppName);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password:');
run.write('test\n');
run.waitSecs(commandTimeoutSecs);
run.matchErr('You need to set a password');
run.matchErr(testUtils.registrationUrlRegexp);
run.expectExit(1);
// After we set a username, we should be able to claim sites.
testUtils.registerWithToken(token, username, 'testtest', email);
run = s.run('claim', legacyAppName);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password: ');
run.write('test\n');
run.waitSecs(commandTimeoutSecs);
run.match('transferred to your account');
run.expectExit(0);
testUtils.cleanUpApp(s, appName);
testUtils.cleanUpApp(s, legacyAppName);
});

View File

@@ -1,22 +1,97 @@
var _ = require('underscore');
var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var files = require('../files.js');
var Sandbox = selftest.Sandbox;
var httpHelpers = require('../http-helpers.js');
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
selftest.define('deploy - expired credentials', ['net', 'slow'], function () {
var s = new Sandbox;
// Create an account and then expire the login token before setting a
// username. On the next deploy, we should get an email prompt
// followed by a registration email, not a username prompt.
var email = testUtils.randomUserEmail();
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
var sessionFile = s.readSessionFile();
testUtils.logout(s);
s.writeSessionFile(sessionFile);
var run = s.run('deploy', appName);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Expired credential');
run.expectExit(1);
// Complete registration so that we can clean up our app.
var username = testUtils.randomString(10);
testUtils.registerWithToken(token, username,
'testtest', email);
testUtils.login(s, username, 'testtest');
testUtils.cleanUpApp(s, appName);
testUtils.logout(s);
// Create an account, set a username, expire the login token, and
// deploy again. We should get a username/password prompt.
email = testUtils.randomUserEmail();
appName = testUtils.randomAppName();
username = testUtils.randomString(10);
token = testUtils.deployWithNewEmail(s, email, appName);
testUtils.registerWithToken(token, username,
'testtest', email);
run = s.run('whoami');
run.waitSecs(commandTimeoutSecs);
run.read(username + '\n');
run.expectExit(0);
sessionFile = s.readSessionFile();
testUtils.logout(s);
s.writeSessionFile(sessionFile);
run = s.run('deploy', appName);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Username:');
run.write(username + '\n');
run.matchErr('Password:');
run.write('testtest' + '\n');
run.waitSecs(90);
run.expectExit(0);
testUtils.cleanUpApp(s, appName);
});
selftest.define('deploy - bad arguments', [], function () {
var s = new Sandbox;
// Deploy with no app name should fail
var run = s.run('deploy');
run.matchErr('not enough arguments');
run.expectExit(1);
// Deploy outside of an app directory
run = s.run('deploy', testUtils.randomAppName());
run.matchErr('not in a Meteor project directory');
run.expectExit(1);
});
selftest.define('deploy - logged in', ['net', 'slow'], function () {
// Create two sandboxes: one with a warehouse so that we can run
// --release, and one without a warehouse so that we run from the
// checkout or release that we started from.
// XXX Is having two sandboxes the only way to do this?
var sandboxWithWarehouse = new Sandbox({
// Include a warehouse arugment so that we can deploy apps with
// --release arguments.
warehouse: {
v1: { tools: 'tool1', latest: true }
}
});
var sandbox = new Sandbox;
var sandboxWithWarehouse;
if (files.inCheckout()) {
sandboxWithWarehouse = new Sandbox({
// Include a warehouse arugment so that we can deploy apps with
// --release arguments.
warehouse: {
v1: { tools: 'tool1', latest: true }
}
});
} else {
sandboxWithWarehouse = new Sandbox;
}
sandbox.createApp('deployapp', 'empty');
sandbox.cd('deployapp');
@@ -35,7 +110,7 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () {
run.expectExit(0);
// And we should have claimed the app by deploying to it.
run = sandbox.run('claim', noPasswordLegacyApp);
run.waitSecs(20);
run.waitSecs(commandTimeoutSecs);
run.matchErr('already belongs to you');
run.expectExit(1);
// Clean up
@@ -48,15 +123,15 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () {
);
// We shouldn't be able to deploy to this app without claiming it
run = sandbox.run('deploy', passwordLegacyApp);
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('meteor claim');
run.expectExit(1);
// If we claim it, we should be able to deploy to it.
run = sandbox.run('claim', passwordLegacyApp);
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password:');
run.write('test\n');
run.waitSecs(10);
run.waitSecs(commandTimeoutSecs);
run.match('successfully transferred to your account');
run.expectExit(0);
run = sandbox.run('deploy', passwordLegacyApp);
@@ -75,7 +150,7 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () {
testUtils.logout(sandbox);
testUtils.login(sandbox, 'testtest', 'testtest');
run = sandbox.run('deploy', appName);
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('belongs to a different user');
run.expectExit(1);
@@ -89,9 +164,14 @@ selftest.define('deploy - logged in', ['net', 'slow'], function () {
selftest.define('deploy - logged out', ['net', 'slow'], function () {
var s = new Sandbox;
var sandboxWithWarehouse = new Sandbox({
warehouse: { v1: { tools: 'tool1', latest: true } }
});
var sandboxWithWarehouse;
if (files.inCheckout()) {
sandboxWithWarehouse = new Sandbox({
warehouse: { v1: { tools: 'tool1', latest: true } }
});
} else {
sandboxWithWarehouse = new Sandbox;
}
testUtils.login(s, 'test', 'testtest');
var appName = testUtils.createAndDeployApp(s);
@@ -100,10 +180,10 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
// Deploy when logged out. We should be prompted to log in and then
// the deploy should succeed.
var run = s.run('deploy', appName);
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Email:');
run.write('test@test.com\n');
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password:');
run.write('testtest\n');
run.waitSecs(90);
@@ -119,10 +199,10 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
sandboxWithWarehouse
);
run = s.run('deploy', legacyNoPassword);
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Email:');
run.write('test@test.com\n');
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password: ');
run.write('testtest\n');
run.waitSecs(90);
@@ -137,15 +217,15 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
'test'
);
run = s.run('deploy', legacyPassword);
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Email:');
// Log in with a username here to test that the email prompt also
// accepts emails. (We put an email in the email prompt above.)
run.write('test\n');
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Password:');
run.write('testtest\n');
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('meteor claim');
run.expectExit(1);
@@ -158,12 +238,12 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
appName = testUtils.randomAppName();
var email = testUtils.randomUserEmail();
run = s.run('deploy', appName);
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Email:');
run.write(email + '\n');
run.waitSecs(90);
run.match('Now serving');
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.expectExit(0);
// Now that we've created an account with this email address, we
// should be logged in as it and should be able to delete it.
@@ -172,11 +252,11 @@ selftest.define('deploy - logged out', ['net', 'slow'], function () {
// Now that we've created a user, try to deploy a new app.
appName = testUtils.randomAppName();
run = s.run('deploy', appName);
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Email:');
run.write(email + '\n');
run.waitSecs(5);
run.matchErr('already in use');
run.waitSecs(commandTimeoutSecs);
run.matchErr('pick a password');
run.matchErr('An email has been sent to you with the link');
run.stop();
});

View File

@@ -0,0 +1,82 @@
var _ = require('underscore');
var selftest = require('../selftest.js');
var testUtils = require('../test-utils.js');
var utils = require('../utils.js');
var Sandbox = selftest.Sandbox;
var httpHelpers = require('../http-helpers.js');
// Poll the given app looking for the correct settings. Throws an error
// if the settings aren't found after a timeout.
var checkForSettings = function (appName, settings, timeoutSecs) {
var timer = setTimeout(function () {
throw new Error('Expected settings not found on app ', appName);
}, timeoutSecs * 1000);
while (true) {
var result = httpHelpers.request('http://' + appName + '.meteor.com');
// XXX This is brittle; the test will break if we start formatting the
// __meteor_runtime_config__ JS differently. Ideally we'd do something
// like point a phantom at the deployed app and actually evaluate
// Meteor.settings.
var configRegexp = /__meteor_runtime_config__ = (.+);<\/script>/;
var configMatch = result.body.match(configRegexp);
if (configMatch && configMatch[1]) {
var stringifiedConfig = configMatch[1].trim();
var parsedConfig = JSON.parse(stringifiedConfig);
if (_.isEqual(parsedConfig.PUBLIC_SETTINGS, settings['public'])) {
clearTimeout(timer);
return;
}
}
}
};
selftest.define('deploy - with settings', ['net', 'slow'], function () {
var s = new Sandbox;
testUtils.login(s, 'test', 'testtest');
var settings = {
'public': { a: 'b' }
};
s.write('settings.json', JSON.stringify(settings));
// Deploy an app with settings and check that the public settings
// appear in the HTTP response body.
var appName = testUtils.createAndDeployApp(s, {
// Use standard-app instead of empty because we actually want
// standard-app-packages (including webapp) so that we can send a
// HTTP request to the app and get a response.
templateApp: 'standard-app',
// The path is ../settings.json instead of settings.json because
// createAndDeployApp creates a new app directory and cd's into it.
settingsFile: '../settings.json'
});
checkForSettings(appName, settings, 10);
// Re-deploy without settings and check that the settings still
// appear.
s.cd('..');
testUtils.createAndDeployApp(s, {
templateApp: 'standard-app',
appName: appName
});
// It takes a few seconds for the app to actually update, and we don't
// want to get a false positive in the meantime (i.e., if the settings
// disappear, we don't want to send our request before the app has
// updated and conclude that the settings are still there).
utils.sleepMs(5000);
checkForSettings(appName, settings, 10);
// Re-deploy with new settings and check that the settings get
// updated.
settings['public'].a = 'c';
s.cd('..');
s.write('settings.json', JSON.stringify(settings));
testUtils.createAndDeployApp(s, {
templateApp: 'standard-app',
settingsFile: '../settings.json',
appName: appName
});
checkForSettings(appName, settings, 10);
testUtils.cleanUpApp(s, appName);
});

View File

@@ -1,5 +1,8 @@
var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var testUtils = require('../test-utils.js');
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
selftest.define("login", ['net'], function () {
var s = new Sandbox;
@@ -11,12 +14,46 @@ selftest.define("login", ['net'], function () {
// Username and password prompts happen on stderr so that scripts can
// run commands that do login interactively and still save the command
// output with the login prompts appearing in it.
//
// Do this twice to confirm that the login command prints a prompt
// even if you are already logged in.
for (var i = 0; i < 2; i++) {
run = s.run("login");
run.matchErr("Username:");
run.write("test\n");
run.matchErr("Password:");
run.write("testtest\n");
run.waitSecs(commandTimeoutSecs);
run.matchErr("Logged in as test.");
run.expectExit(0);
}
// Leaving username blank, or getting the password wrong, doesn't
// reprompt. It also doesn't log you out.
run = s.run("login");
run.matchErr("Username:");
run.write("\n");
run.matchErr("Password:");
run.write("whatever\n");
run.waitSecs(commandTimeoutSecs);
run.matchErr("failed");
run.expectExit(1);
run = s.run("login");
run.matchErr("Username:");
run.write("test\n");
run.matchErr("Password:");
run.write("whatever\n");
run.waitSecs(commandTimeoutSecs);
run.matchErr("failed");
run.expectExit(1);
run = s.run('login');
run.matchErr("Username:");
run.write("test\n");
run.matchErr("Password:");
run.write("testtest\n");
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Logged in as test.");
run.expectExit(0);
@@ -28,12 +65,12 @@ selftest.define("login", ['net'], function () {
run.expectExit(0);
run = s.run("logout");
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Logged out");
run.expectExit(0);
run = s.run("logout");
run.waitSecs(1);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Not logged in");
run.expectExit(0);
@@ -47,7 +84,7 @@ selftest.define("login", ['net'], function () {
run.write("test\n");
run.matchErr("Password:");
run.write("badpassword\n");
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Login failed");
run.expectExit(1);
@@ -58,12 +95,12 @@ selftest.define("login", ['net'], function () {
run.write("TeSt\n");
run.matchErr("Password:");
run.write("testtest\n");
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Logged in as test.");
run.expectExit(0);
run = s.run("logout");
run.waitSecs(2);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Logged out");
run.expectExit(0);
@@ -74,7 +111,29 @@ selftest.define("login", ['net'], function () {
run.write("test\n");
run.matchErr("Password:");
run.write("TesTTesT\n");
run.waitSecs(5);
run.waitSecs(commandTimeoutSecs);
run.matchErr("Login failed");
run.expectExit(1);
});
selftest.define('whoami - no username', ['net', 'slow'], function () {
var s = new Sandbox;
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
var run = s.run('whoami');
run.waitSecs(commandTimeoutSecs);
run.matchErr('You haven\'t chosen your username yet');
run.matchErr(testUtils.registrationUrlRegexp);
run.expectExit(1);
testUtils.registerWithToken(token, username, 'test', email);
run = s.run('whoami');
run.waitSecs(commandTimeoutSecs);
run.read(username);
run.expectExit(0);
testUtils.cleanUpApp(s, appName);
});

View File

@@ -1,6 +1,7 @@
var _ = require('underscore');
var selftest = require('../selftest.js');
var Sandbox = selftest.Sandbox;
var testUtils = require('../test-utils.js');
// XXX need to make sure that mother doesn't clean up:
// 'legacy-password-app-for-selftest'
@@ -8,6 +9,8 @@ var Sandbox = selftest.Sandbox;
// 'app-for-selftest-not-test-owned'
// 'app-for-selftest-test-owned'
var commandTimeoutSecs = testUtils.accountsCommandTimeoutSecs;
// Run 'meteor logs' or 'meteor mongo' against an app. Options:
// - legacy: boolean
@@ -37,7 +40,7 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) {
}
var run = sandbox.run.apply(sandbox, runArgs);
run.waitSecs(10);
run.waitSecs(commandTimeoutSecs);
var expectSuccess = selftest.markStack(function () {
run.match(matchString);
@@ -68,11 +71,26 @@ var logsOrMongoForApp = function (sandbox, command, appName, options) {
} else {
// If we are not logged in and this is not a legacy app, then we
// expect a login prompt.
//
// (If testReprompt is true, try getting reprompted as a result
// of entering no username or a bad password.)
if (options.testReprompt) {
run.matchErr('Username: ');
run.write("\n");
run.matchErr("Username:");
run.write(" \n");
}
run.matchErr('Username: ');
run.write((options.username || 'test') + '\n');
if (options.testReprompt) {
run.matchErr("Password:");
run.write("wrongpassword\n");
run.waitSecs(15);
run.matchErr("failed");
}
run.matchErr('Password: ');
run.write((options.password || 'testtest') + '\n');
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
if (options.authorized) {
expectSuccess();
} else {
@@ -90,18 +108,33 @@ _.each([false, true], function (loggedIn) {
['net'],
function () {
var s = new Sandbox;
var run;
if (loggedIn) {
var run = s.run('login');
run.waitSecs(2);
run = s.run('login');
run.waitSecs(commandTimeoutSecs);
run.matchErr('Username:');
run.write('test\n');
run.matchErr('Password:');
run.write('testtest\n');
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Logged in as test.');
run.expectExit(0);
}
// Running 'meteor logs' without an app name should fail.
if (command === 'logs') {
run = s.run(command);
run.matchErr('not enough arguments');
run.expectExit(1);
}
// Running 'meteor mongo' without an app name and not in an app
// dir should fail.
if (command === 'mongo') {
run = s.run('mongo');
run.matchErr('not in a Meteor project directory');
run.expectExit(1);
}
logsOrMongoForApp(s, command,
'legacy-no-password-app-for-selftest', {
legacy: true,
@@ -119,14 +152,15 @@ _.each([false, true], function (loggedIn) {
logsOrMongoForApp(s, command,
'app-for-selftest-not-test-owned', {
loggedIn: loggedIn,
authorized: false
authorized: false,
testReprompt: true
});
if (! loggedIn) {
// We logged in as a result of running the previous command,
// so log out again.
run = s.run('logout');
run.waitSecs(15);
run.waitSecs(commandTimeoutSecs);
run.matchErr('Logged out');
run.expectExit(0);
}

View File

@@ -25,11 +25,13 @@ selftest.define("npm", ["net"], function () {
run.tellMongo(MONGO_LISTENING);
if (i === 0) {
run.waitSecs(2);
// use match instead of read because on a built release we can
// also get an update message here.
run.read(
"npm-test: updating npm dependencies -- meteor-test-executable...\n");
}
run.waitSecs(15);
run.read("null; From shell script\n");
run.match("null; From shell script\n");
run.expectEnd();
run.expectExit(0);
});

View File

@@ -4,21 +4,20 @@ var testUtils = require('../test-utils.js');
var utils = require('../utils.js');
var Sandbox = selftest.Sandbox;
var httpHelpers = require('../http-helpers.js');
var release = require('../release.js');
var unipackage = require('../unipackage.js');
var config = require('../config.js');
var getLoadedPackages = _.once(function () {
return unipackage.load({
library: release.current.library,
packages: ['meteor', 'livedata'],
release: release.current.name
});
});
var ddpConnect = function (url) {
var DDP = getLoadedPackages().livedata.DDP;
return DDP.connect(url);
var expectInvalidToken = function (token) {
// Same XXX as testUtils.registerWithToken: should be hardcoded to
// https://www.meteor.com?
var accountsConn = testUtils.ddpConnect(config.getAuthDDPUrl());
var registrationTokenInfo = accountsConn.call('registrationTokenInfo',
token);
// We should not be able to get a registration code for an invalid
// token.
if (registrationTokenInfo.valid || registrationTokenInfo.code) {
throw new Error('Expected invalid token is valid!');
}
accountsConn.close();
};
// Polls a guerrillamail.com inbox every 3 seconds looking for an email
@@ -96,12 +95,61 @@ var waitForEmail = selftest.markStack(function (inbox, subjectRegExp,
return match;
});
selftest.define('deferred registration', ['net'], function () {
selftest.define('deferred registration - email registration token', ['net', 'slow'], function () {
var s = new Sandbox;
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var apiToken = testUtils.deployWithNewEmail(s, email, appName);
// Check that we got a registration email in our inbox.
var registrationEmail = waitForEmail(email, /Set a password/,
/set a password/, 60);
// Fish out the registration token and use to it to complete
// registration.
var token = testUtils.registrationUrlRegexp.exec(registrationEmail.bodyPage);
if (! token || ! token[1]) {
throw new Error("No registration token in email");
}
token = token[1];
testUtils.registerWithToken(token, username, 'testtest', email);
// Success! 'meteor whoami' should now know who we are.
run = s.run('whoami');
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
run.read(username + '\n');
run.expectExit(0);
// We should be able to log out and log back in with our new password.
testUtils.logout(s);
testUtils.login(s, username, 'testtest');
// And after logging out and logging back in, we should have
// authorization to delete our app.
testUtils.cleanUpApp(s, appName);
// All the tokens we got should now be invalid.
expectInvalidToken(token);
expectInvalidToken(apiToken);
// XXX Test that registration URLs get printed when they should
});
selftest.define('deferred registration revocation', ['net'], function () {
// Test that if we are logged in as a passwordless user, and our
// credential gets revoked, and we do something like 'meteor whoami'
// that polls to see if registration is complete, then we handle it
// gracefully.
var s = new Sandbox;
s.createApp('deployapp', 'empty');
s.cd('deployapp');
// Deploy an app with a new email address.
// Create a new deferred registration account. (Don't bother to wait
// for the deploy to go through.)
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
@@ -110,52 +158,114 @@ selftest.define('deferred registration', ['net'], function () {
run.matchErr('Email:');
run.write(email + '\n');
run.waitSecs(90);
// Check that we got a prompt to set a password on meteor.com.
run.matchErr('set a password');
run.matchErr('https://www.meteor.com');
run.match('Deploying');
run.waitSecs(15); // because the bundler doesn't yield
run.stop();
// 'whoami' says that we don't have a password
run = s.run('whoami');
run.waitSecs(15);
run.matchErr('/setPassword?');
run.expectExit(1);
// Revoke the credential without updating .meteorsession.
var sessionState = s.readSessionFile();
run = s.run('logout');
run.waitSecs(15);
run.readErr("Logged out.\n");
run.expectEnd();
run.expectExit(0);
s.writeSessionFile(sessionState);
// Check that we got a registration email in our inbox.
var registrationEmail = waitForEmail(email, /Set a password/,
/set a password/, 60);
// Fish out the registration token and use to it to complete
// registration.
var token = /\/setPassword\?([a-zA-Z0-9\+\/]+)/.
exec(registrationEmail.bodyPage);
if (! token || ! token[1]) {
throw new Error("No registration token in email");
}
token = token[1];
// XXX It might make more sense to hard-code the DDP url to
// https://www.meteor.com, since that's who the sandboxes are talking
// to.
var accountsConn = ddpConnect(config.getAuthDDPUrl());
var registrationTokenInfo = accountsConn.call('registrationTokenInfo',
token);
var registrationCode = registrationTokenInfo.code;
accountsConn.call('register', {
username: username,
password: 'testtest',
emails: [email],
token: token,
code: registrationCode
});
accountsConn.close();
// Success! We should be able to log out and log back in with our new
// password.
testUtils.logout(s);
testUtils.login(s, username, 'testtest');
// And after logging out and logging back in, we should have
// authorization to delete our app.
testUtils.cleanUpApp(s, appName);
// XXX Test the api registration URLs
// XXX Test that registration URLs get printed when they should
// XXX Test registration while the tool is waiting on a DDP method to
// return (e.g. deploy and login with an existing username that
// doesn't have a password set yet)
// 'whoami' now says that we're not logged in. No errors are printed.
run = s.run('whoami');
run.waitSecs(15);
run.readErr("Not logged in. 'meteor login' to log in.\n");
run.expectEnd();
run.expectExit(1);
});
selftest.define(
'deferred registration - api registration token',
['net', 'slow'],
function () {
var s = new Sandbox;
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
testUtils.registerWithToken(token, username, 'testtest', email);
testUtils.logout(s);
testUtils.login(s, username, 'testtest');
testUtils.cleanUpApp(s, appName);
// All tokens we received should not be invalid.
expectInvalidToken(token);
var registrationEmail = waitForEmail(email, /Set a password/,
/set a password/, 60);
var emailToken = testUtils.registrationUrlRegexp.exec(
registrationEmail.bodyPage
);
if (! emailToken || ! emailToken[1]) {
throw new Error('No registration token in email');
}
expectInvalidToken(emailToken[1]);
}
);
selftest.define(
'deferred registration - register after logging out',
['net', 'slow'],
function () {
var s = new Sandbox;
var email = testUtils.randomUserEmail();
var username = testUtils.randomString(10);
var appName = testUtils.randomAppName();
var token = testUtils.deployWithNewEmail(s, email, appName);
testUtils.logout(s);
// If we deploy again with the same email address after logging out,
// we should get a message telling us to check our email and
// register, and the tool should obediently wait for us to do that
// before doing the deploy.
s.createApp('deployapp2', 'empty');
s.cd('deployapp2');
var run = s.run('deploy', appName);
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
run.matchErr('Email:');
run.write(email + '\n');
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
run.matchErr('pick a password');
run.matchErr('Waiting for you to register on the web...');
var registrationEmail = waitForEmail(
email,
/Set a password/,
/You previously created a Meteor developer account/,
60
);
token = testUtils.registrationUrlRegexp.exec(
registrationEmail.bodyPage
);
if (! token || ! token[1]) {
throw new Error('No registration token in email');
}
testUtils.registerWithToken(token[1], username, 'testtest', email);
run.waitSecs(testUtils.accountsCommandTimeoutSecs);
run.matchErr('Username: ' + username + '\n');
run.matchErr('Password: ');
run.write('testtest\n');
run.waitSecs(90);
run.match('Now serving at');
run.expectExit(0);
run = s.run('whoami');
run.read(username);
run.expectExit(0);
testUtils.cleanUpApp(s, appName);
}
);

View File

@@ -92,6 +92,12 @@ selftest.define("springboard", ['checkout'], function () {
run.read('v2\ntools2\n');
run.expectEnd();
run.expectExit(0);
// .meteor/release exists but is empty. You get an error.
s.write(".meteor/release", "\n");
run = s.run("list", "--using");
run.matchErr("release file which is empty");
run.expectExit(1);
});
});

View File

@@ -44,6 +44,7 @@ selftest.define("run", function () {
// Crashes
s.write("crash.js", "process.exit(42);");
run.waitSecs(5);
run.match("with code: 42");
run.waitSecs(5);
run.match("is crashing");
@@ -171,7 +172,7 @@ selftest.define("run --once", function () {
s.cd("onceapp");
s.set("RUN_ONCE_OUTCOME", "mongo");
run = s.run("--once");
run.waitSecs(5);
run.waitSecs(15);
run.expectExit(86);
});

View File

@@ -71,9 +71,9 @@ var check = function (showBanner) {
if (manifest.releases.stable.banner &&
warehouse.lastPrintedBannerRelease() !== manifestLatestRelease) {
if (showBanner) {
runLog.log();
runLog.log("");
runLog.log(manifest.releases.stable.banner);
runLog.log();
runLog.log("");
}
warehouse.writeLastPrintedBannerRelease(manifestLatestRelease);
} else {

View File

@@ -341,11 +341,24 @@ _.extend(warehouse, {
// (whether or not we just downloaded them). (Don't do this if we didn't
// print the installing message!)
if (newPieces && showInstalling) {
var unlinkIfExists = function (file) {
try {
fs.unlinkSync(file);
} catch (e) {
// If two processes populate the warehouse in parallel, the other
// process may have deleted the fresh file. That's OK!
if (e.code === "ENOENT")
return;
throw e;
}
};
if (newPieces.tools) {
fs.unlinkSync(warehouse.getToolsFreshFile(newPieces.tools.version));
unlinkIfExists(warehouse.getToolsFreshFile(newPieces.tools.version));
}
_.each(newPieces.packages, function (packageInfo, name) {
fs.unlinkSync(warehouse.getPackageFreshFile(name, packageInfo.version));
unlinkIfExists(
warehouse.getPackageFreshFile(name, packageInfo.version));
});
}