infinite-loop 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +79 -0
- data/Rakefile +2 -0
- data/bin/hockey +21 -0
- data/infinite-loop.gemspec +22 -0
- data/lib/infinite.rb +4 -0
- data/lib/infinite/loop.rb +3 -0
- data/lib/infinite/loop/agent.rb +62 -0
- data/lib/infinite/loop/commands.rb +6 -0
- data/lib/infinite/loop/commands/apps.rb +28 -0
- data/lib/infinite/loop/commands/devices.rb +74 -0
- data/lib/infinite/loop/commands/login.rb +24 -0
- data/lib/infinite/loop/commands/logout.rb +15 -0
- data/lib/infinite/loop/helpers.rb +28 -0
- metadata +95 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Thomas Dohmke
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# Infinite::Loop
|
2
|
+
|
3
|
+
### CLI for HockeyApp
|
4
|
+
|
5
|
+
List apps and devices from HockeyApp. Highly inspired by the great [Cupertino](https://github.com/mattt/cupertino).
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
```sh
|
10
|
+
$ gem install infinite-loop
|
11
|
+
```
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
### Fetch or Create an API token
|
16
|
+
|
17
|
+
```sh
|
18
|
+
$ hockey login
|
19
|
+
```
|
20
|
+
|
21
|
+
This asks you for your email and password. It then checks if you have any existing read-only tokens and stores the first in ~/.netrc. If not, it creates one.
|
22
|
+
|
23
|
+
### List of Apps
|
24
|
+
|
25
|
+
```sh
|
26
|
+
$ hockey apps:list
|
27
|
+
```
|
28
|
+
|
29
|
+
### List of Devices
|
30
|
+
|
31
|
+
```sh
|
32
|
+
$ hockey devices:list APP_ID
|
33
|
+
```
|
34
|
+
|
35
|
+
If you only want devices which are not in your current provisioning profile, add "unprovisioned":
|
36
|
+
|
37
|
+
```sh
|
38
|
+
$ hockey devices:list APP_ID unprovisioned
|
39
|
+
```
|
40
|
+
|
41
|
+
### Loop Over Devices and Add to Provisioning Portal
|
42
|
+
|
43
|
+
This works only if you have already set up cupertino:
|
44
|
+
|
45
|
+
```sh
|
46
|
+
$ gem install cupertino
|
47
|
+
$ ios login
|
48
|
+
```
|
49
|
+
|
50
|
+
Then you can show all device (or all unprovisioned devices, see above) and add them to the portal:
|
51
|
+
|
52
|
+
```sh
|
53
|
+
$ hockey devices:loop APP_ID
|
54
|
+
```
|
55
|
+
|
56
|
+
Finally edit your profile:
|
57
|
+
|
58
|
+
```sh
|
59
|
+
$ ios profiles:manage:devices distribution
|
60
|
+
```
|
61
|
+
|
62
|
+
## TODO
|
63
|
+
|
64
|
+
- Download profiles from the Provisioning Portal
|
65
|
+
- Upload modified profiles to HockeyApp
|
66
|
+
- Token management
|
67
|
+
- Some error handling
|
68
|
+
|
69
|
+
## Contact
|
70
|
+
|
71
|
+
Thomas Dohmke & HockeyApp
|
72
|
+
|
73
|
+
- http://twitter.com/ashtom
|
74
|
+
- http://twitter.com/hockeyapp
|
75
|
+
- support@hockeyapp.net
|
76
|
+
|
77
|
+
## License
|
78
|
+
|
79
|
+
Infinite Loop is available under the MIT license. See the LICENSE file for more info.
|
data/Rakefile
ADDED
data/bin/hockey
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'commander/import'
|
4
|
+
require 'terminal-table'
|
5
|
+
require 'term/ansicolor'
|
6
|
+
require 'netrc'
|
7
|
+
|
8
|
+
require 'infinite'
|
9
|
+
|
10
|
+
HighLine.track_eof = false # Fix for built-in Ruby
|
11
|
+
|
12
|
+
program :version, Infinite::VERSION
|
13
|
+
program :description, 'A command-line interface for HockeyApp'
|
14
|
+
|
15
|
+
program :help, 'Author', 'Thomas Dohmke <thomas@hockeyapp.net>'
|
16
|
+
program :help, 'Website', 'http://hockeyapp.net'
|
17
|
+
program :help_formatter, :compact
|
18
|
+
|
19
|
+
default_command :help
|
20
|
+
|
21
|
+
require 'infinite/loop'
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "infinite"
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Thomas Dohmke"]
|
7
|
+
gem.email = ["thomas@dohmke.de"]
|
8
|
+
gem.description = "A command-line interface for HockeyApp"
|
9
|
+
gem.summary = "Infinite Loop"
|
10
|
+
gem.homepage = ""
|
11
|
+
|
12
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
gem.name = "infinite-loop"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Infinite::VERSION
|
18
|
+
|
19
|
+
gem.add_dependency "cupertino"
|
20
|
+
gem.add_dependency "rest-client"
|
21
|
+
gem.add_dependency "json"
|
22
|
+
end
|
data/lib/infinite.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require "json"
|
2
|
+
require "rest-client"
|
3
|
+
|
4
|
+
module Infinite
|
5
|
+
module Loop
|
6
|
+
class Agent
|
7
|
+
BASE_URL = "https://rink.hockeyapp.net/api/2/"
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
netrc = Netrc.read
|
11
|
+
@token = netrc[Infinite::HOSTNAME].first unless netrc[Infinite::HOSTNAME].nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def list_apps(release_type_string = nil)
|
15
|
+
response = RestClient.get "#{BASE_URL}apps", { "X-HockeyAppToken" => @token }
|
16
|
+
apps = JSON.parse(response)["apps"]
|
17
|
+
apps.reject! { |app| app["release_type"] != release_type(release_type_string) } unless release_type_string.nil?
|
18
|
+
apps
|
19
|
+
end
|
20
|
+
|
21
|
+
def list_devices(app_id, unprovisioned = nil)
|
22
|
+
response = RestClient.get "#{BASE_URL}apps/#{app_id}/devices?unprovisioned=#{unprovisioned.to_s == "unprovisioned" ? 1 : 0}", {"X-HockeyAppToken" => @token }
|
23
|
+
devices = JSON.parse(response)["devices"]
|
24
|
+
devices
|
25
|
+
end
|
26
|
+
|
27
|
+
def list_tokens(user, pass)
|
28
|
+
response = RestClient::Request.new(:method => :get, :url => "#{BASE_URL}auth_tokens", :user => user, :password => pass).execute
|
29
|
+
tokens = JSON.parse(response.to_str)["tokens"]
|
30
|
+
tokens
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_token(user, pass)
|
34
|
+
response = RestClient::Request.new(:method => :post, :url => "#{BASE_URL}auth_tokens", :user => user, :password => pass).execute
|
35
|
+
tokens = JSON.parse(response.to_str)["tokens"]
|
36
|
+
tokens
|
37
|
+
end
|
38
|
+
|
39
|
+
def release_type(string)
|
40
|
+
case string
|
41
|
+
when "beta"
|
42
|
+
0
|
43
|
+
when "live"
|
44
|
+
1
|
45
|
+
when "alpha"
|
46
|
+
2
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def release_type_string(value)
|
51
|
+
case value.to_i
|
52
|
+
when 0
|
53
|
+
"beta"
|
54
|
+
when 1
|
55
|
+
"live"
|
56
|
+
when 2
|
57
|
+
"alpha"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
command :'apps:list' do |c|
|
2
|
+
c.syntax = 'hockey apps:list'
|
3
|
+
c.summary = 'Lists the Name and ID of Apps on HockeyApp'
|
4
|
+
c.description = ''
|
5
|
+
|
6
|
+
c.action do |args, options|
|
7
|
+
if args.nil? or args.empty?
|
8
|
+
apps = try { agent.list_apps }
|
9
|
+
else
|
10
|
+
apps = try { agent.list_apps(args.first) }
|
11
|
+
end
|
12
|
+
|
13
|
+
title = "Listing Apps"
|
14
|
+
table = Terminal::Table.new :title => title do |t|
|
15
|
+
t << ["Title", "Platform", "Release Type", "App ID"]
|
16
|
+
t.add_separator
|
17
|
+
apps.compact.each do |app|
|
18
|
+
t << [
|
19
|
+
app["title"],
|
20
|
+
app["platform"],
|
21
|
+
agent.release_type_string(app["release_type"]),
|
22
|
+
app["public_identifier"]
|
23
|
+
]
|
24
|
+
end
|
25
|
+
puts t
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
command :'devices:list' do |c|
|
2
|
+
c.syntax = 'hockey devices:list'
|
3
|
+
c.summary = 'Lists the Devices of an App on HockeyApp'
|
4
|
+
c.description = ''
|
5
|
+
|
6
|
+
c.action do |args, options|
|
7
|
+
say_error "Missing arguments, expected App ID" and abort if args.nil? or args.empty?
|
8
|
+
|
9
|
+
devices = try { agent.list_devices(args[0], (args.length > 1 ? args[1] : nil)) }
|
10
|
+
title = "Listing Devices"
|
11
|
+
table = Terminal::Table.new :title => title do |t|
|
12
|
+
t << ["Device Name", "Device Identifier"]
|
13
|
+
t.add_separator
|
14
|
+
devices.compact.each do |device|
|
15
|
+
t << [
|
16
|
+
device["name"],
|
17
|
+
device["udid"]
|
18
|
+
]
|
19
|
+
end
|
20
|
+
puts t
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
command :'devices:loop' do |c|
|
26
|
+
c.syntax = 'hockey devices:list'
|
27
|
+
c.summary = 'Add the Devices of an App on HockeyApp to the Provisioning Portal'
|
28
|
+
c.description = ''
|
29
|
+
|
30
|
+
c.action do |args, options|
|
31
|
+
say_error "Missing arguments, expected App ID" and abort if args.nil? or args.empty?
|
32
|
+
|
33
|
+
devices = try { agent.list_devices(args[0], (args.length > 1 ? args[1] : nil)) }
|
34
|
+
title = "Listing Devices"
|
35
|
+
table = Terminal::Table.new :title => title do |t|
|
36
|
+
t << ["#", "Device Name", "Device Identifier"]
|
37
|
+
t.add_separator
|
38
|
+
index = 0
|
39
|
+
devices.compact.each do |device|
|
40
|
+
t << [
|
41
|
+
"#{index + 1}",
|
42
|
+
device["name"],
|
43
|
+
device["udid"]
|
44
|
+
]
|
45
|
+
index += 1
|
46
|
+
end
|
47
|
+
|
48
|
+
looping = true
|
49
|
+
while looping
|
50
|
+
puts t
|
51
|
+
selected = ask "Add device by #, or all devices (a), or quit (q):"
|
52
|
+
puts
|
53
|
+
|
54
|
+
case selected
|
55
|
+
when 'q'
|
56
|
+
add = nil
|
57
|
+
looping = false
|
58
|
+
when 'a'
|
59
|
+
add = devices
|
60
|
+
else
|
61
|
+
add = [devices[selected.to_i - 1]] if selected.to_i >= 1 and selected.to_i <= index
|
62
|
+
end
|
63
|
+
|
64
|
+
unless add.nil?
|
65
|
+
ext_args = []
|
66
|
+
add.each do |device|
|
67
|
+
ext_args << "\"#{device["name"]}=#{device["udid"]}\""
|
68
|
+
end
|
69
|
+
system "ios devices:add #{ext_args.join(" ")}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
command :login do |c|
|
2
|
+
c.syntax = 'hockey login'
|
3
|
+
c.summary = 'Sign in to HockeyApp and fetch or create token'
|
4
|
+
c.description = ''
|
5
|
+
|
6
|
+
c.action do |args, options|
|
7
|
+
say_warning "You are already authenticated" if Netrc.read[Infinite::HOSTNAME]
|
8
|
+
|
9
|
+
user = ask "Email:"
|
10
|
+
pass = password "Password:"
|
11
|
+
|
12
|
+
tokens = agent.list_tokens(user, pass)
|
13
|
+
tokens.reject! { |token| token["rights"].to_i != 2 }
|
14
|
+
if tokens.length == 0
|
15
|
+
tokens = agent.create_token(user, pass)
|
16
|
+
end
|
17
|
+
|
18
|
+
netrc = Netrc.read
|
19
|
+
netrc[Infinite::HOSTNAME] = tokens.first["token"], "X"
|
20
|
+
netrc.save
|
21
|
+
|
22
|
+
say_ok "API token saved"
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
command :logout do |c|
|
2
|
+
c.syntax = 'hockey logout'
|
3
|
+
c.summary = 'Remove API token'
|
4
|
+
c.description = ''
|
5
|
+
|
6
|
+
c.action do |args, options|
|
7
|
+
say_error "You are not authenticated" and abort unless Netrc.read[Infinite::HOSTNAME]
|
8
|
+
|
9
|
+
netrc = Netrc.read
|
10
|
+
netrc.delete(Infinite::HOSTNAME)
|
11
|
+
netrc.save
|
12
|
+
|
13
|
+
say_ok "API token removed"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class String
|
2
|
+
include Term::ANSIColor
|
3
|
+
end
|
4
|
+
|
5
|
+
module Infinite
|
6
|
+
module Loop
|
7
|
+
module Helpers
|
8
|
+
def agent
|
9
|
+
@agent = Infinite::Loop::Agent.new unless @agent
|
10
|
+
@agent
|
11
|
+
end
|
12
|
+
|
13
|
+
def pluralize(n, singular, plural = nil)
|
14
|
+
n.to_i == 1 ? "1 #{singular}" : "#{n} #{plural || singular + 's'}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def try
|
18
|
+
return unless block_given?
|
19
|
+
|
20
|
+
begin
|
21
|
+
yield
|
22
|
+
#rescue UnsuccessfulAuthenticationError
|
23
|
+
# say_error "Could not authenticate with HockeyApp." and abort
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: infinite-loop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Thomas Dohmke
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-07-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: cupertino
|
16
|
+
requirement: &70213216552340 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70213216552340
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rest-client
|
27
|
+
requirement: &70213216551520 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70213216551520
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &70213216550540 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70213216550540
|
47
|
+
description: A command-line interface for HockeyApp
|
48
|
+
email:
|
49
|
+
- thomas@dohmke.de
|
50
|
+
executables:
|
51
|
+
- hockey
|
52
|
+
extensions: []
|
53
|
+
extra_rdoc_files: []
|
54
|
+
files:
|
55
|
+
- .gitignore
|
56
|
+
- Gemfile
|
57
|
+
- LICENSE
|
58
|
+
- README.md
|
59
|
+
- Rakefile
|
60
|
+
- bin/hockey
|
61
|
+
- infinite-loop.gemspec
|
62
|
+
- lib/infinite.rb
|
63
|
+
- lib/infinite/loop.rb
|
64
|
+
- lib/infinite/loop/agent.rb
|
65
|
+
- lib/infinite/loop/commands.rb
|
66
|
+
- lib/infinite/loop/commands/apps.rb
|
67
|
+
- lib/infinite/loop/commands/devices.rb
|
68
|
+
- lib/infinite/loop/commands/login.rb
|
69
|
+
- lib/infinite/loop/commands/logout.rb
|
70
|
+
- lib/infinite/loop/helpers.rb
|
71
|
+
homepage: ''
|
72
|
+
licenses: []
|
73
|
+
post_install_message:
|
74
|
+
rdoc_options: []
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
none: false
|
79
|
+
requirements:
|
80
|
+
- - ! '>='
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
requirements: []
|
90
|
+
rubyforge_project:
|
91
|
+
rubygems_version: 1.8.17
|
92
|
+
signing_key:
|
93
|
+
specification_version: 3
|
94
|
+
summary: Infinite Loop
|
95
|
+
test_files: []
|