mirror of
https://github.com/meteor/meteor.git
synced 2026-05-02 03:01:46 -04:00
Merge branch 'master' into devel
Conflicts: History.md
This commit is contained in:
12
.mailmap
12
.mailmap
@@ -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>
|
||||
|
||||
285
History.md
285
History.md
@@ -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
|
||||
|
||||
144
LICENSE.txt
144
LICENSE.txt
@@ -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/
|
||||
----------
|
||||
|
||||
@@ -1 +1 @@
|
||||
sso-1
|
||||
0.7.1.1
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.0.1
|
||||
0.7.1.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.0.1
|
||||
0.7.1.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.0.1
|
||||
0.7.1.1
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.0.1
|
||||
0.7.1.1
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
1
packages/cookies/.gitignore
vendored
1
packages/cookies/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
.build*
|
||||
@@ -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(";");
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 });
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
@@ -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']);
|
||||
});
|
||||
@@ -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) {
|
||||
|
||||
2
packages/jquery-waypoints/package.js
vendored
2
packages/jquery-waypoints/package.js
vendored
@@ -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) {
|
||||
|
||||
2
packages/less/less_tests_empty.less
Normal file
2
packages/less/less_tests_empty.less
Normal file
@@ -0,0 +1,2 @@
|
||||
#id {}
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
|
||||
@@ -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
|
||||
});
|
||||
});;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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'.
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
259
tools/auth.js
259
tools/auth.js
@@ -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) {
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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: " +
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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]);
|
||||
};
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
82
tools/tests/deploy-settings.js
Normal file
82
tools/tests/deploy-settings.js
Normal 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);
|
||||
});
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user