applebot 0.0.1
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.
- checksums.yaml +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/LICENSE +19 -0
- data/README.md +153 -0
- data/Rakefile +13 -0
- data/applebot.gemspec +27 -0
- data/bin/applebot +73 -0
- data/create_app_id_manifest.js +6 -0
- data/create_manifest.js +24 -0
- data/lib/applebot/command_proxy.rb +44 -0
- data/lib/applebot/commands.rb +74 -0
- data/lib/applebot/error.rb +97 -0
- data/lib/applebot/mkmf.rb +25 -0
- data/lib/applebot/shell.rb +79 -0
- data/lib/applebot/version.rb +5 -0
- data/lib/applebot.rb +109 -0
- data/phantom/_commands.json +318 -0
- data/phantom/applebot.js +656 -0
- data/phantom/create_app.js +355 -0
- data/phantom/create_app_id.js +80 -0
- data/phantom/create_profile.js +134 -0
- data/phantom/delete_app_id.js +57 -0
- data/phantom/download_profile.js +36 -0
- data/phantom/list_app_id.js +66 -0
- data/phantom/list_profile.js +23 -0
- data/phantom/reject_binary_app.js +73 -0
- data/phantom/remove_from_sale_app.js +50 -0
- data/phantom/update_app.js +309 -0
- data/update_app_manifest.js +9 -0
- metadata +143 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8abd1f581c359df890dca0e9ed9a458d6244a41d
|
4
|
+
data.tar.gz: 1af37a02a80c482bc8da15de976853215d4f203b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5eb30d8e6205b5d25347ea4c16953d6540ba276fa92ccab84ea659d019f2dfd3386c7cf06fddb461b98a2f5dac4c4bcbf3a1b2b6e7a12d5c837433135d3b41b
|
7
|
+
data.tar.gz: 8078823f751ddf30c2ffc578fd3273c151c9e01a735e8f1f719ada1dd7117e4ba0808436475dd3fc7721e58752fc62e6e41a047f8247bec527a6155f8437a9c1
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
applebot (0.0.1)
|
5
|
+
activesupport (>= 3.2)
|
6
|
+
commander (~> 4.1.2)
|
7
|
+
terminal-table (>= 1.4.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
activesupport (4.0.3)
|
13
|
+
i18n (~> 0.6, >= 0.6.4)
|
14
|
+
minitest (~> 4.2)
|
15
|
+
multi_json (~> 1.3)
|
16
|
+
thread_safe (~> 0.1)
|
17
|
+
tzinfo (~> 0.3.37)
|
18
|
+
atomic (1.1.14)
|
19
|
+
commander (4.1.6)
|
20
|
+
highline (~> 1.6.11)
|
21
|
+
dotenv (0.10.0)
|
22
|
+
highline (1.6.21)
|
23
|
+
i18n (0.6.9)
|
24
|
+
minitest (4.7.5)
|
25
|
+
multi_json (1.9.3)
|
26
|
+
rake (10.1.0)
|
27
|
+
terminal-table (1.4.5)
|
28
|
+
thread_safe (0.1.3)
|
29
|
+
atomic
|
30
|
+
tzinfo (0.3.38)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
applebot!
|
37
|
+
dotenv
|
38
|
+
rake
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2013-2014 Turboprop Inc (https://usepropeller.com/)
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
# AppleBot - The Apple Robot
|
2
|
+
|
3
|
+
A CLI and Ruby library to manage Apple Developer Center and iTunes Connect tasks. Uses [CasperJS](http://casperjs.org/) for easy maintenance and flexibility.
|
4
|
+
|
5
|
+
## Requirements
|
6
|
+
|
7
|
+
AppleBot requires [CasperJS](http://casperjs.org/), which can be installed using [Homebrew](http://brew.sh/):
|
8
|
+
|
9
|
+
```
|
10
|
+
$ brew install casperjs --devel
|
11
|
+
```
|
12
|
+
|
13
|
+
## Installation
|
14
|
+
|
15
|
+
```
|
16
|
+
$ gem install applebot
|
17
|
+
```
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
### Commands
|
22
|
+
|
23
|
+
These commands run using iTunes Connect:
|
24
|
+
|
25
|
+
- `app:create` - Creates an entry for a new app on iTunes Connect
|
26
|
+
- `app:update` - Creates an entry for a new update to an existing app on iTunes Connect
|
27
|
+
- `app:reject_binary` - Rejects the binary for a pending release (either new or an update)
|
28
|
+
- `app:remove_from_sale` - Removes the app from sale in all territories
|
29
|
+
|
30
|
+
These commands run using the Apple Developer Center:
|
31
|
+
|
32
|
+
- `app_id:create` - Creates a bundle identifier
|
33
|
+
- `app_id:delete` - Removes a bundle identifier
|
34
|
+
- `profile:create` - Creates a provisioning profile
|
35
|
+
- `profile:download` - Downloads an existing provisioning profile
|
36
|
+
- `profile:list` - Lists all provisioning profiles
|
37
|
+
|
38
|
+
These commands can run on either iTunes Connect or Apple Developer Center:
|
39
|
+
|
40
|
+
- `app_id:list` - Lists all bundle identifiers
|
41
|
+
|
42
|
+
#### JSON Manifests
|
43
|
+
|
44
|
+
Most commands take in options - you can either pass them individually, or use a JSON manifest file like this:
|
45
|
+
|
46
|
+
```json
|
47
|
+
{
|
48
|
+
"name": "My new app",
|
49
|
+
"app_id": "com.usepropeller.mynewapp"
|
50
|
+
}
|
51
|
+
```
|
52
|
+
|
53
|
+
For example, the following commands are equivalent usng this JSON manifest:
|
54
|
+
|
55
|
+
```shell
|
56
|
+
$ applebot app_id:create --name 'My new app' --app_id 'com.usepropeller.mynewapp'
|
57
|
+
$ applebot app_id:create --manifest ./manifest.json
|
58
|
+
```
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
> AppleBot.app_id.create(name: 'My new app', app_id: 'com.usepropeller.mynewapp')
|
62
|
+
> AppleBot.app_id.create(manifest './manifest.json')
|
63
|
+
```
|
64
|
+
|
65
|
+
### CLI
|
66
|
+
|
67
|
+
AppleBot installs an `applebot` command, which you can explore with `-h` flags:
|
68
|
+
|
69
|
+
```shell
|
70
|
+
$ applebot -h
|
71
|
+
|
72
|
+
Commands:
|
73
|
+
app:create Create App
|
74
|
+
app:reject_binary Reject the binary of an pending release
|
75
|
+
app:remove_from_sale Remove app from sale
|
76
|
+
app:update Update App
|
77
|
+
app_id:create Create App ID
|
78
|
+
app_id:delete Delete App ID
|
79
|
+
app_id:list List App IDs
|
80
|
+
help Display global or [command] help documentation.
|
81
|
+
profile:create Create Provisioning Profile
|
82
|
+
profile:download Download a Provisioning Profile
|
83
|
+
profile:list List Provisioning Profiles
|
84
|
+
|
85
|
+
Global Options:
|
86
|
+
--manifest FILE.json Use a JSON file to load options for each command
|
87
|
+
--username USERNAME Username to login to Apple Service, or $APPLEBOT_USERNAME
|
88
|
+
--password PASSWORD Password to login to Apple Service, or $APPLEBOT_PASSWORD
|
89
|
+
--format FORMAT Output format - ['json', 'pretty']
|
90
|
+
--verbose Verbose output
|
91
|
+
```
|
92
|
+
|
93
|
+
#### Authentication
|
94
|
+
|
95
|
+
For every command, you can pass `--username` and `--password` flags to enter you auth credentials; you can also set `APPLEBOT_USERNAME` and `APPLEBOT_PASSWORD` environment variables.
|
96
|
+
|
97
|
+
### Ruby
|
98
|
+
|
99
|
+
The Ruby library uses an `AppleBot` module, and its methods map to the CLI commands:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
> require 'applebot'
|
103
|
+
=> true
|
104
|
+
> AppleBot.app.create(options: here)
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Authentication
|
108
|
+
|
109
|
+
The Ruby library has a few shortcuts for logging in to Apple services:
|
110
|
+
|
111
|
+
```ruby
|
112
|
+
# pass as options
|
113
|
+
AppleBot.app.create(username: "username", password: "password")
|
114
|
+
|
115
|
+
# run in block
|
116
|
+
AppleBot.with_credentials(username: "username", password: "password") do
|
117
|
+
AppleBot.app.create(options)
|
118
|
+
end
|
119
|
+
|
120
|
+
# set globally
|
121
|
+
AppleBot.set_credentials(username: "username", password: "password")
|
122
|
+
```
|
123
|
+
|
124
|
+
|
125
|
+
### Output
|
126
|
+
|
127
|
+
The `:list` commands are meant to return some data. If you're using the Ruby library, you'll receive an `Array` when the command is done; if you're using the CLI, the last line will output a JSON object with one entry.
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
AppleBot.app_ids.all
|
131
|
+
=> ["com.usepropeller.myapp"]
|
132
|
+
```
|
133
|
+
|
134
|
+
```bash
|
135
|
+
$ applebot app_ids:all
|
136
|
+
{"app_ids": ["com.usepropeller.myapp"]}
|
137
|
+
```
|
138
|
+
|
139
|
+
If you're using any other command (which generally create side-effects), the end result will be `true` in Ruby, or exit code 0 on the CLI.
|
140
|
+
|
141
|
+
#### Verbose & Pretty Output
|
142
|
+
|
143
|
+
You can base a `--verbose` flag (or a `verbose: true` option in Ruby) to see all of the output as each script processes. There are two output formats, `json` and `pretty`, which you are set with either the `--format` flag or `format: 'format_string'` in Ruby.
|
144
|
+
|
145
|
+
## Contact
|
146
|
+
|
147
|
+
[Clay Allsopp](http://clayallsopp.com/)
|
148
|
+
- [clay@usepropeller.com](mailto:clay@usepropeller.com)
|
149
|
+
- [@clayallsopp](https://twitter.com/clayallsopp)
|
150
|
+
|
151
|
+
## License
|
152
|
+
|
153
|
+
AppleBot is available under the MIT license. See the [LICENSE](LICENSE) file for more info.
|
data/Rakefile
ADDED
data/applebot.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
require "applebot/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "applebot"
|
8
|
+
s.license = "MIT"
|
9
|
+
s.authors = ["Clay Allsopp"]
|
10
|
+
s.email = "clay@usepropeller.com"
|
11
|
+
s.homepage = AppleBot::WEBSITE
|
12
|
+
s.version = AppleBot::VERSION
|
13
|
+
s.summary = "AppleBot"
|
14
|
+
s.description = AppleBot::DESCRIPTION
|
15
|
+
|
16
|
+
s.add_dependency "commander", "~> 4.1.2"
|
17
|
+
s.add_dependency "activesupport", ">= 3.2"
|
18
|
+
s.add_dependency "terminal-table", ">= 1.4.0"
|
19
|
+
|
20
|
+
s.add_development_dependency "rake"
|
21
|
+
s.add_development_dependency "dotenv"
|
22
|
+
|
23
|
+
s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
|
24
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
25
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
end
|
data/bin/applebot
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'commander/import'
|
4
|
+
|
5
|
+
applebot = File.join(File.dirname(__FILE__), '..', 'lib', 'applebot')
|
6
|
+
require applebot
|
7
|
+
|
8
|
+
HighLine.track_eof = false # Fix for built-in Ruby
|
9
|
+
Signal.trap("INT") {} # Suppress backtrace when exiting command
|
10
|
+
|
11
|
+
program :name, 'AppleBot'
|
12
|
+
program :version, AppleBot::VERSION
|
13
|
+
program :description, AppleBot::DESCRIPTION
|
14
|
+
|
15
|
+
program :help, 'Author', 'Clay Allsopp <clay@usepropeller.com>'
|
16
|
+
program :help, 'Website', AppleBot::WEBSITE
|
17
|
+
program :help_formatter, :compact
|
18
|
+
|
19
|
+
default_command :help
|
20
|
+
|
21
|
+
global_option('--manifest FILE.json', 'Use a JSON file to load options for each command') { |file|
|
22
|
+
$manifest_file = file
|
23
|
+
}
|
24
|
+
|
25
|
+
global_option('--username USERNAME', 'Username to login to Apple Service, or $APPLEBOT_USERNAME') { |username|
|
26
|
+
$username = username
|
27
|
+
}
|
28
|
+
|
29
|
+
global_option('--password PASSWORD', 'Password to login to Apple Service, or $APPLEBOT_PASSWORD') { |password|
|
30
|
+
$password = password
|
31
|
+
}
|
32
|
+
|
33
|
+
global_option('--format FORMAT', "Output format - 'json' or 'pretty'") { |format|
|
34
|
+
$format = format
|
35
|
+
}
|
36
|
+
|
37
|
+
global_option('--verbose', "Verbose output") {|verbose|
|
38
|
+
$verbose = verbose
|
39
|
+
}
|
40
|
+
|
41
|
+
AppleBot.commands.each do |apple_command|
|
42
|
+
command apple_command.cli_command.to_sym do |c|
|
43
|
+
c.syntax = "applebot #{apple_command.cli_command} [options]"
|
44
|
+
c.description = apple_command.description
|
45
|
+
c.summary = apple_command.description
|
46
|
+
|
47
|
+
(apple_command.options.required + apple_command.options.optional).each do |apple_option|
|
48
|
+
c.option "--#{apple_option.key} VALUE", apple_option.cli_description
|
49
|
+
end
|
50
|
+
|
51
|
+
c.action do |args, options|
|
52
|
+
user_options = {
|
53
|
+
manifest: $manifest_file,
|
54
|
+
format: $format || 'pretty',
|
55
|
+
verbose: $verbose,
|
56
|
+
print_result: true
|
57
|
+
}.merge(options.__hash__)
|
58
|
+
|
59
|
+
credentials = {username: $username, password: $password}
|
60
|
+
|
61
|
+
ret_value = false
|
62
|
+
AppleBot.with_credentials(credentials) do |ab|
|
63
|
+
ret_value = ab.run_command(apple_command.ruby_method, user_options)
|
64
|
+
end
|
65
|
+
$username = nil
|
66
|
+
$password = nil
|
67
|
+
$manifest_file = nil
|
68
|
+
$format = nil
|
69
|
+
$verbose = nil
|
70
|
+
ret_value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/create_manifest.js
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
{
|
2
|
+
"username": "clay+applestore@usepropeller.com",
|
3
|
+
"password": "B0nnyB3ar",
|
4
|
+
"title": "Test App 9007",
|
5
|
+
"sku": "TEST_APP_9007",
|
6
|
+
"id": "com.usepropeller.test.test9007",
|
7
|
+
"price_tier": "free",
|
8
|
+
"app_version": "1.0.0",
|
9
|
+
"keywords": "this,thing,cool",
|
10
|
+
"support_url": "http://usepropeller.com",
|
11
|
+
"description": "My cool app",
|
12
|
+
"simulated_gambling": "none",
|
13
|
+
"horror_fear_themes": "none",
|
14
|
+
"primary_category": "book",
|
15
|
+
"secondary_category": "catalogs",
|
16
|
+
"first_name": "Clay",
|
17
|
+
"last_name": "Allsopp",
|
18
|
+
"email": "Clay.allsopp@gmail.com",
|
19
|
+
"phone": "9197937595",
|
20
|
+
"copyright": "Thing 2013",
|
21
|
+
"large_icon": "/Users/clayallsopp/Desktop/itunes_1.jpg",
|
22
|
+
"screenshots_35": "[\"/Users/clayallsopp/Projects/Apptory/iOS/profiles/apps/705/screenshots/products_35.png\"]",
|
23
|
+
"screenshots_4": "[\"/Users/clayallsopp/Projects/Apptory/iOS/profiles/apps/705/screenshots/products_4.png\"]"
|
24
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module AppleBot
|
2
|
+
class CommandProxy
|
3
|
+
class_attribute :proxies
|
4
|
+
self.proxies = {}
|
5
|
+
|
6
|
+
attr_accessor :namespace, :commands
|
7
|
+
|
8
|
+
def self.for(command)
|
9
|
+
proxies[command.namespace] ||= new(command.namespace)
|
10
|
+
proxy = proxies[command.namespace]
|
11
|
+
proxy.add_command(command)
|
12
|
+
proxy
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize(namespace)
|
16
|
+
@namespace = namespace
|
17
|
+
@commands = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def add_command(command)
|
21
|
+
return if @commands.include?(command.action)
|
22
|
+
@commands << command.action
|
23
|
+
define_singleton_method(command.action, ->(options = {}) {
|
24
|
+
AppleBot.run_command(command.file_name, options)
|
25
|
+
})
|
26
|
+
end
|
27
|
+
|
28
|
+
def attach_to(klass)
|
29
|
+
return if klass.respond_to?(self.namespace)
|
30
|
+
namespace = self.namespace
|
31
|
+
klass.define_singleton_method(namespace) {
|
32
|
+
return AppleBot::CommandProxy.proxies[namespace]
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def inspect
|
37
|
+
s = self.to_s.gsub(">", " ")
|
38
|
+
s << "commands: #{@commands.join(', ')}"
|
39
|
+
s << ">"
|
40
|
+
s
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module AppleBot
|
2
|
+
mattr_accessor :commands
|
3
|
+
|
4
|
+
class Command < Struct.new(:file_name, :namespace, :action, :description, :options)
|
5
|
+
def initialize(*properties)
|
6
|
+
options = properties.delete_at -1
|
7
|
+
super(*properties)
|
8
|
+
self.options = CommandOptionSet.new(options['required'], options['optional'])
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.load_all!
|
12
|
+
commands_file_path = File.join(AppleBot.phantom_scripts_path, '_commands.json')
|
13
|
+
commands = JSON.parse(IO.read(commands_file_path))
|
14
|
+
AppleBot.commands = commands.keys.map {|file|
|
15
|
+
command_config = commands[file]
|
16
|
+
args = [file] + [
|
17
|
+
command_config['namespace'], command_config['action'],
|
18
|
+
command_config['description'], command_config['options']
|
19
|
+
]
|
20
|
+
Command.new(*args)
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def cli_command
|
25
|
+
"#{namespace}:#{action}"
|
26
|
+
end
|
27
|
+
|
28
|
+
def ruby_method
|
29
|
+
"#{action}_#{namespace}"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class CommandOptionSet < Struct.new(:required, :optional)
|
34
|
+
def initialize(required, optional)
|
35
|
+
fix_batch_options = lambda { |set, extras|
|
36
|
+
set.map {|o|
|
37
|
+
if o['batch']
|
38
|
+
o['keys'].map {|k|
|
39
|
+
h = {
|
40
|
+
'key' => k
|
41
|
+
}.merge(o)
|
42
|
+
h.delete 'keys'
|
43
|
+
h.delete 'batch'
|
44
|
+
h
|
45
|
+
}
|
46
|
+
else
|
47
|
+
o
|
48
|
+
end
|
49
|
+
}.flatten.map {|o|
|
50
|
+
o = o.merge(extras)
|
51
|
+
CommandOption.new(o['key'], o['description'], o['values'], o['default'], o['required'])
|
52
|
+
}
|
53
|
+
}
|
54
|
+
required = fix_batch_options.call(required, {'required' => true}) if required
|
55
|
+
required ||= []
|
56
|
+
optional = fix_batch_options.call(optional, {'required' => false}) if optional
|
57
|
+
optional ||= []
|
58
|
+
super(required, optional)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class CommandOption < Struct.new(:key, :description, :values, :default, :required)
|
63
|
+
def cli_description
|
64
|
+
s = ""
|
65
|
+
s << "REQUIRED " if required
|
66
|
+
s << "#{description}"
|
67
|
+
s.tap do |d|
|
68
|
+
d << " - VALUES: #{values}" if values
|
69
|
+
d << " - DEFAULT: #{default}" if default
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
AppleBot::Command.load_all!
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module AppleBot
|
2
|
+
class AppleBotError < StandardError
|
3
|
+
class_attribute :message
|
4
|
+
self.message = "Unspecified AppleBot error occured"
|
5
|
+
attr_reader :output, :html
|
6
|
+
|
7
|
+
def initialize(error, output = [], message = nil)
|
8
|
+
super(self.class.message)
|
9
|
+
output ||= []
|
10
|
+
output = output.map(&:strip).reject(&:blank?)
|
11
|
+
@html = output.select {|n|
|
12
|
+
is_debug_html?(n)
|
13
|
+
}.map { |s|
|
14
|
+
JSON.parse(s)['html']
|
15
|
+
}.first
|
16
|
+
|
17
|
+
@output = output.reject {|n|
|
18
|
+
is_debug_html?(n)
|
19
|
+
}
|
20
|
+
|
21
|
+
full_backtrace = (@output.map { |o|
|
22
|
+
"#{o}:in `AppleBot'"
|
23
|
+
} + error.backtrace).compact
|
24
|
+
set_backtrace(full_backtrace)
|
25
|
+
end
|
26
|
+
|
27
|
+
def is_debug_html?(line)
|
28
|
+
line.include?('"event":"debug_html"')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class AppIdNotFound < AppleBotError
|
33
|
+
self.message = "Could not find App Bundle ID in the HTML"
|
34
|
+
end
|
35
|
+
|
36
|
+
class AppleMaintenanceMode < AppleBotError
|
37
|
+
self.message = "Apple is currently in maintenance mode"
|
38
|
+
end
|
39
|
+
|
40
|
+
class AuthenticationError < AppleBotError
|
41
|
+
self.message = "Provided Apple ID credentials were incorrect"
|
42
|
+
end
|
43
|
+
|
44
|
+
class MultipleProfileError < AppleBotError
|
45
|
+
self.message = "Attempted to re-create an existing profile"
|
46
|
+
end
|
47
|
+
|
48
|
+
class AppNameTakenError < AppleBotError
|
49
|
+
self.message = "The app name you entered is taken"
|
50
|
+
end
|
51
|
+
|
52
|
+
class SignalTermination < StandardError
|
53
|
+
def initialize(status)
|
54
|
+
super("The process was terminated with signal #{status.termsig} (#{Signal.signame(status.termsig)})")
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
MESSAGE_FRAGMENT_TO_ERROR = {
|
59
|
+
"could not find App ID in options" => AppIdNotFound,
|
60
|
+
'class="maintenance"' => AppleMaintenanceMode,
|
61
|
+
'iTunes Connect is temporarily unavailable' => AppleMaintenanceMode,
|
62
|
+
'Your Apple ID or password was entered incorrectly' => AuthenticationError,
|
63
|
+
'Multiple profiles found with the name' => MultipleProfileError,
|
64
|
+
'The App Name you entered has already been used' => AppNameTakenError,
|
65
|
+
}
|
66
|
+
|
67
|
+
class AppleBotError
|
68
|
+
def self.for_output(output = [])
|
69
|
+
output ||= []
|
70
|
+
|
71
|
+
error_class = nil
|
72
|
+
|
73
|
+
output.each { |line|
|
74
|
+
MESSAGE_FRAGMENT_TO_ERROR.each { |message, klass|
|
75
|
+
if line.include?(message)
|
76
|
+
error_class = klass
|
77
|
+
break
|
78
|
+
end
|
79
|
+
}
|
80
|
+
break if error_class
|
81
|
+
}
|
82
|
+
error_class ||= AppleBotError
|
83
|
+
|
84
|
+
begin
|
85
|
+
# capture the Ruby-level stack trace
|
86
|
+
raise StandardError
|
87
|
+
rescue Exception => e
|
88
|
+
error_class.new(e, output)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.test
|
93
|
+
raise for_output(["something", "happened"])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module AppleBot
|
2
|
+
module_function
|
3
|
+
|
4
|
+
# ripped from mkmf source
|
5
|
+
def find_executable(bin, path = nil)
|
6
|
+
ext = ""
|
7
|
+
if File.expand_path(bin) == bin
|
8
|
+
return bin if File.executable?(bin)
|
9
|
+
ext and File.executable?(file = bin + ext) and return file
|
10
|
+
return nil
|
11
|
+
end
|
12
|
+
if path ||= ENV['PATH']
|
13
|
+
path = path.split(File::PATH_SEPARATOR)
|
14
|
+
else
|
15
|
+
path = %w[/usr/local/bin /usr/ucb /usr/bin /bin]
|
16
|
+
end
|
17
|
+
file = nil
|
18
|
+
path.each do |dir|
|
19
|
+
return file if File.executable?(file = File.join(dir, bin))
|
20
|
+
return file if ext and File.executable?(file << ext)
|
21
|
+
end
|
22
|
+
nil
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module AppleBot
|
2
|
+
class Shell
|
3
|
+
def command(sys_command, verbose, format)
|
4
|
+
output = []
|
5
|
+
Open3.popen3(sys_command) do |stdin, stdout, stderr, wait_thr|
|
6
|
+
while line = (stdout.gets || stderr.gets)
|
7
|
+
output << line
|
8
|
+
if verbose === true
|
9
|
+
puts_format_line(line, format)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
exit_status = wait_thr.value
|
13
|
+
output << JSON.generate(process_status: exit_status.inspect, thread: wait_thr.inspect)
|
14
|
+
if exit_status.termsig
|
15
|
+
raise SignalTermination.new(exit_status)
|
16
|
+
end
|
17
|
+
unless exit_status.success?
|
18
|
+
raise AppleBotError.for_output(output)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
output.select {|o|
|
23
|
+
o.include?('"result":')
|
24
|
+
}.last
|
25
|
+
end
|
26
|
+
|
27
|
+
def puts_format_line(line, format)
|
28
|
+
json = JSON.parse(line)
|
29
|
+
|
30
|
+
case format.to_s
|
31
|
+
when 'json'
|
32
|
+
puts JSON.generate(json.except('normal_output'))
|
33
|
+
else
|
34
|
+
normal_output = json['normal_output']
|
35
|
+
return if normal_output.blank?
|
36
|
+
return if normal_output.include?("[phantom]")
|
37
|
+
puts(normal_output)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def result(output, format, print_result)
|
42
|
+
result = JSON.parse(output)['result']
|
43
|
+
|
44
|
+
case format.to_s
|
45
|
+
when 'pretty'
|
46
|
+
table(result).tap do |t|
|
47
|
+
puts t if print_result
|
48
|
+
end
|
49
|
+
true
|
50
|
+
else
|
51
|
+
result.tap do |json|
|
52
|
+
puts json if print_result
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def table(data)
|
58
|
+
table = nil
|
59
|
+
if data.first && data.first[-1].is_a?(Hash)
|
60
|
+
headings = ['key'] + data.first[-1].keys
|
61
|
+
rows = data.to_a.map { |key_and_value|
|
62
|
+
row = []
|
63
|
+
headings.each do |heading|
|
64
|
+
if heading == 'key'
|
65
|
+
row << key_and_value[0]
|
66
|
+
else
|
67
|
+
row << key_and_value[1][heading]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
row
|
71
|
+
}
|
72
|
+
table = Terminal::Table.new headings: headings, rows: rows
|
73
|
+
else
|
74
|
+
table = Terminal::Table.new rows: data.to_a
|
75
|
+
end
|
76
|
+
table
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|