pilot 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +216 -13
- data/bin/pilot +4 -3
- data/lib/assets/XMLTemplate.xml.erb +12 -0
- data/lib/pilot.rb +11 -3
- data/lib/pilot/build_manager.rb +87 -0
- data/lib/pilot/commands_generator.rb +123 -0
- data/lib/pilot/manager.rb +61 -0
- data/lib/pilot/options.rb +88 -0
- data/lib/pilot/package_builder.rb +42 -0
- data/lib/pilot/tester_exporter.rb +38 -0
- data/lib/pilot/tester_importer.rb +47 -0
- data/lib/pilot/tester_manager.rb +137 -0
- data/lib/pilot/version.rb +2 -1
- metadata +64 -27
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3a252d1f974787effe53dd857fabc664f5266bd9
|
4
|
+
data.tar.gz: 7c878a216f10538ce757c88f4cb66bcf047316ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c41ab0d075f516c1764755ea9c5f986e815884a53f91622c5f0790c50508d3b0f3bc6af7e326f57d44560778d05cd1d92451e1f20bc18670e6c3172142639def
|
7
|
+
data.tar.gz: 8f46022dac75b6c64e5082f2447214d17eda3df4bb2496c375ccae279af49624e658112c5d9a6d45b9add0c50530d0e913eb6d99709acacf3fd79b3a3d4c8444
|
data/README.md
CHANGED
@@ -13,43 +13,246 @@
|
|
13
13
|
<a href="https://github.com/KrauseFx/sigh">sigh</a> •
|
14
14
|
<a href="https://github.com/KrauseFx/produce">produce</a> •
|
15
15
|
<a href="https://github.com/KrauseFx/cert">cert</a> •
|
16
|
-
<a href="https://github.com/KrauseFx/codes">codes</a>
|
16
|
+
<a href="https://github.com/KrauseFx/codes">codes</a> •
|
17
|
+
<a href="https://github.com/fastlane/spaceship">spaceship</a> •
|
18
|
+
<b>pilot</b> •
|
19
|
+
<a href="https://github.com/fastlane/boarding">boarding</a>
|
17
20
|
</p>
|
18
21
|
-------
|
19
22
|
|
20
23
|
<p align="center">
|
21
|
-
<img src="assets/
|
24
|
+
<img src="assets/PilotTextTransparentSmall.png" width="500">
|
22
25
|
</p>
|
23
26
|
|
24
|
-
|
27
|
+
Pilot
|
25
28
|
============
|
29
|
+
[![Twitter: @KauseFx](https://img.shields.io/badge/contact-@KrauseFx-blue.svg?style=flat)](https://twitter.com/KrauseFx)
|
30
|
+
[![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/fastlane/pilot/blob/master/LICENSE)
|
31
|
+
[![Gem](https://img.shields.io/gem/v/pilot.svg?style=flat)](http://rubygems.org/gems/pilot)
|
26
32
|
|
27
|
-
This gem allows you to manage all important features of Apple TestFlight using a CLI.
|
28
33
|
|
29
|
-
|
34
|
+
###### The best way to manage your TestFlight testers and builds from your terminal
|
35
|
+
|
36
|
+
This tool allows you to manage all important features of Apple TestFlight using your terminal.
|
30
37
|
|
31
38
|
- Upload new builds and distribute them to all testers
|
32
|
-
-
|
33
|
-
- Add
|
39
|
+
- List all available builds
|
40
|
+
- Add and remove beta testers
|
41
|
+
- Get information about testers, like the registered devices
|
42
|
+
- Export and import all your testers
|
43
|
+
|
44
|
+
Get in contact with the developer on Twitter: [@KrauseFx](https://twitter.com/KrauseFx)
|
45
|
+
|
46
|
+
`pilot` uses [spaceship.airforce](https://spaceship.airforce) to interact with iTunes Connect :rocket:
|
47
|
+
|
48
|
+
-------
|
49
|
+
<p align="center">
|
50
|
+
<a href="#installation">Installation</a> •
|
51
|
+
<a href="#usage">Usage</a> •
|
52
|
+
<a href="#tips">Tips</a> •
|
53
|
+
<a href="#need-help">Need help?</a>
|
54
|
+
</p>
|
55
|
+
|
56
|
+
-------
|
57
|
+
|
58
|
+
<h5 align="center"><code>pilot</code> is part of <a href="https://fastlane.tools">fastlane</a>: connect all deployment tools into one streamlined workflow.</h5>
|
59
|
+
|
60
|
+
# Installation
|
61
|
+
|
62
|
+
sudo gem install pilot
|
63
|
+
|
64
|
+
# Usage
|
65
|
+
|
66
|
+
For all commands you can specify the Apple ID to use using `-u felix@krausefx.com`. If you execute `pilot` in a project already using [fastlane](https://fastlane.tools) the username and app identifier will automatically be determined.
|
67
|
+
|
68
|
+
## Uploading builds
|
34
69
|
|
35
70
|
To upload a new build, just run
|
36
71
|
|
37
72
|
```
|
38
|
-
|
73
|
+
pilot upload
|
39
74
|
```
|
40
75
|
|
41
76
|
This will automatically look for an `ipa` in your current directory and tries to fetch the login credentials from your [fastlane setup](https://fastlane.tools).
|
42
77
|
|
43
|
-
You'll be asked for any missing information. Additionally, you can pass all kinds of parameters to `
|
78
|
+
You'll be asked for any missing information. Additionally, you can pass all kinds of parameters to `pilot`:
|
44
79
|
|
45
80
|
```
|
46
|
-
|
47
|
-
--changelog "This build is better"
|
81
|
+
pilot upload -u felix@krausefx.com
|
48
82
|
```
|
49
83
|
|
50
|
-
`
|
84
|
+
Additionally you can skip the submission of the binary, which means, the `ipa` file will only be uploaded and not distributet to testers:
|
85
|
+
|
86
|
+
```
|
87
|
+
pilot upload --skip_submission
|
88
|
+
```
|
89
|
+
|
90
|
+
`pilot` does all kinds of magic for you:
|
51
91
|
|
52
92
|
- Automatically detects the bundle identifier from your `ipa` file
|
53
93
|
- Automatically fetch the AppID of your app based on the bundle identifier
|
54
94
|
|
55
|
-
|
95
|
+
`pilot` uses [spaceship.airforce](https://spaceship.airforce) to submit the build metadata and the iTunes Transporter to upload the binary :rocket:
|
96
|
+
|
97
|
+
## List builds
|
98
|
+
|
99
|
+
To list all builds for specific application use
|
100
|
+
|
101
|
+
```
|
102
|
+
pilot list
|
103
|
+
```
|
104
|
+
|
105
|
+
The result lists all active builds and processing builds:
|
106
|
+
|
107
|
+
```
|
108
|
+
+-----------+---------+----------+----------+----------+
|
109
|
+
| Great App Builds |
|
110
|
+
+-----------+---------+----------+----------+----------+
|
111
|
+
| Version # | Build # | Testing | Installs | Sessions |
|
112
|
+
+-----------+---------+----------+----------+----------+
|
113
|
+
| 0.9.13 | 1 | Expired | 1 | 0 |
|
114
|
+
| 0.9.13 | 2 | Expired | 0 | 0 |
|
115
|
+
| 0.9.20 | 3 | Expired | 0 | 0 |
|
116
|
+
| 0.9.20 | 4 | Internal | 5 | 3 |
|
117
|
+
+-----------+---------+----------+----------+----------+
|
118
|
+
```
|
119
|
+
|
120
|
+
## Managing beta testers
|
121
|
+
|
122
|
+
### List of Testers
|
123
|
+
|
124
|
+
This command will list all your testers, both internal and external.
|
125
|
+
|
126
|
+
```
|
127
|
+
pilot list
|
128
|
+
```
|
129
|
+
|
130
|
+
The output will look like this:
|
131
|
+
|
132
|
+
```
|
133
|
+
+--------+--------+--------------------------+-----------+
|
134
|
+
| Internal Testers |
|
135
|
+
+--------+--------+--------------------------+-----------+
|
136
|
+
| First | Last | Email | # Devices |
|
137
|
+
+--------+--------+--------------------------+-----------+
|
138
|
+
| Felix | Krause | felix@krausefx.com | 2 |
|
139
|
+
+--------+--------+--------------------------+-----------+
|
140
|
+
|
141
|
+
+-----------+---------+----------------------------+-----------+
|
142
|
+
| External Testers |
|
143
|
+
+-----------+---------+----------------------------+-----------+
|
144
|
+
| First | Last | Email | # Devices |
|
145
|
+
+-----------+---------+----------------------------+-----------+
|
146
|
+
| Max | Manfred | email@email.com | 0 |
|
147
|
+
| Detlef | Müller | detlef@krausefx.com | 1 |
|
148
|
+
+-----------+---------+----------------------------+-----------+
|
149
|
+
```
|
150
|
+
|
151
|
+
### Add a new tester
|
152
|
+
|
153
|
+
To add a new tester to both your iTunes Connect account and to your app (if given), use the `pilot add` command. This will create a new tester (if necesssary) or add an existing tester to the app to test.
|
154
|
+
|
155
|
+
```
|
156
|
+
pilot add email@invite.com
|
157
|
+
```
|
158
|
+
|
159
|
+
Additionally you can specify the app identifier (if necessary):
|
160
|
+
|
161
|
+
```
|
162
|
+
pilot add -e email@email.com -a com.krausefx.app
|
163
|
+
```
|
164
|
+
|
165
|
+
### Find a tester
|
166
|
+
|
167
|
+
To find a specific tester use
|
168
|
+
|
169
|
+
```
|
170
|
+
pilot find felix@krausefx.com
|
171
|
+
```
|
172
|
+
|
173
|
+
The resulting output will look like this:
|
174
|
+
|
175
|
+
```
|
176
|
+
+---------------------+---------------------+
|
177
|
+
| felix@krausefx.com |
|
178
|
+
+---------------------+---------------------+
|
179
|
+
| First name | Felix |
|
180
|
+
| Last name | Krause |
|
181
|
+
| Email | felix@krausefx.com |
|
182
|
+
| Latest Version | 0.9.14 (23 |
|
183
|
+
| Latest Install Date | 03/28/15 19:00 |
|
184
|
+
| 2 Devices | • iPhone 6, iOS 8.3 |
|
185
|
+
| | • iPhone 5, iOS 7.0 |
|
186
|
+
+---------------------+---------------------+
|
187
|
+
```
|
188
|
+
|
189
|
+
### Remove a tester
|
190
|
+
|
191
|
+
This command will only remove external beta testers.
|
192
|
+
|
193
|
+
```
|
194
|
+
pilot remove felix@krausefx.com
|
195
|
+
```
|
196
|
+
|
197
|
+
### Export testers
|
198
|
+
|
199
|
+
To export all external testers to a CSV file. Useful if you need to import tester info to another system or a new account.
|
200
|
+
|
201
|
+
```
|
202
|
+
pilot export
|
203
|
+
```
|
204
|
+
|
205
|
+
### Import testers
|
206
|
+
|
207
|
+
Add external testers from a CSV file. Sample CSV file available [here](https://itunesconnect.apple.com/itc/docs/tester_import.csv).
|
208
|
+
|
209
|
+
```
|
210
|
+
pilot import
|
211
|
+
```
|
212
|
+
|
213
|
+
You can also specify the directory using
|
214
|
+
|
215
|
+
```
|
216
|
+
pilot export -c ~/Desktop/testers.csv
|
217
|
+
pilot import -c ~/Desktop/testers.csv
|
218
|
+
```
|
219
|
+
|
220
|
+
# Tips
|
221
|
+
|
222
|
+
## [`fastlane`](https://fastlane.tools) Toolchain
|
223
|
+
|
224
|
+
- [`fastlane`](https://fastlane.tools): Connect all deployment tools into one streamlined workflow
|
225
|
+
- [`deliver`](https://github.com/KrauseFx/deliver): Upload screenshots, metadata and your app to the App Store using a single command
|
226
|
+
- [`snapshot`](https://github.com/KrauseFx/snapshot): Automate taking localized screenshots of your iOS app on every device
|
227
|
+
- [`frameit`](https://github.com/KrauseFx/frameit): Quickly put your screenshots into the right device frames
|
228
|
+
- [`PEM`](https://github.com/KrauseFx/pem): Automatically generate and renew your push notification profiles
|
229
|
+
- [`produce`](https://github.com/KrauseFx/produce): Create new iOS apps on iTunes Connect and Dev Portal using the command line
|
230
|
+
- [`cert`](https://github.com/KrauseFx/cert): Automatically create and maintain iOS code signing certificates
|
231
|
+
- [`spaceship`](https://github.com/fastlane/spaceship): Ruby library to access the Apple Dev Center and iTunes Connect
|
232
|
+
- [`pilot`](https://github.com/fastlane/pilot): The best way to manage your TestFlight testers and builds from your terminal
|
233
|
+
- [`boarding`](https://github.com/fastlane/boarding): The easiest way to invite your TestFlight beta testers
|
234
|
+
|
235
|
+
##### [Like this tool? Be the first to know about updates and new fastlane tools](https://tinyletter.com/krausefx)
|
236
|
+
|
237
|
+
## How is my password stored?
|
238
|
+
|
239
|
+
`pilot` uses the [CredentialsManager](https://github.com/fastlane/CredentialsManager) from `fastlane`.
|
240
|
+
|
241
|
+
# Need help?
|
242
|
+
- If there is a technical problem with `pilot`, submit an issue.
|
243
|
+
- I'm available for contract work - drop me an email: pilot@krausefx.com
|
244
|
+
|
245
|
+
# License
|
246
|
+
This project is licensed under the terms of the MIT license. See the LICENSE file.
|
247
|
+
|
248
|
+
> This project and all fastlane tools are in no way affiliated with Apple Inc. This project is open source under the MIT license, which means you have full access to the source code and can modify it to fit your own needs. All fastlane tools run on your own computer or server, so your credentials or other sensitive information will never leave your own computer. You are responsible for how you use fastlane tools.
|
249
|
+
|
250
|
+
# Contributing
|
251
|
+
|
252
|
+
1. Create an issue to start a discussion about your idea
|
253
|
+
2. Fork it (https://github.com/fastlane/pilot/fork)
|
254
|
+
3. Create your feature branch (`git checkout -b my-new-feature`)
|
255
|
+
4. Commit your changes (`git commit -am 'Add some feature'`)
|
256
|
+
5. Push to the branch (`git push origin my-new-feature`)
|
257
|
+
6. Create a new Pull Request
|
258
|
+
|
data/bin/pilot
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
|
2
|
+
$LOAD_PATH.push File.expand_path("../../lib", __FILE__)
|
3
3
|
|
4
|
-
require
|
5
|
-
|
4
|
+
require "pilot"
|
5
|
+
require "pilot/commands_generator"
|
6
|
+
Pilot::CommandsGenerator.start
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
+
<package xmlns="http://apple.com/itunes/importer" version="software4.7">
|
3
|
+
<software_assets apple_id="<%= @data[:apple_id] %>">
|
4
|
+
<asset type="bundle">
|
5
|
+
<data_file>
|
6
|
+
<size><%= @data[:file_size] %></size>
|
7
|
+
<file_name><%= @data[:ipa_path] %></file_name>
|
8
|
+
<checksum type="md5"><%= @data[:md5] %></checksum>
|
9
|
+
</data_file>
|
10
|
+
</asset>
|
11
|
+
</software_assets>
|
12
|
+
</package>
|
data/lib/pilot.rb
CHANGED
@@ -1,7 +1,15 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "json"
|
2
|
+
require "pilot/version"
|
3
|
+
require "pilot/manager"
|
4
|
+
require "pilot/build_manager"
|
5
|
+
require "pilot/tester_manager"
|
6
|
+
require "pilot/tester_importer"
|
7
|
+
require "pilot/tester_exporter"
|
8
|
+
require "pilot/package_builder"
|
3
9
|
|
4
|
-
require
|
10
|
+
require "fastlane_core"
|
11
|
+
require "spaceship"
|
12
|
+
require "terminal-table"
|
5
13
|
|
6
14
|
module Pilot
|
7
15
|
Helper = FastlaneCore::Helper # you gotta love Ruby: Helper.* should use the Helper class contained in FastlaneCore
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Pilot
|
2
|
+
class BuildManager < Manager
|
3
|
+
def upload(options)
|
4
|
+
start(options)
|
5
|
+
|
6
|
+
Helper.log.info "Ready to upload new build to TestFlight (App: #{config[:apple_id]})...".green
|
7
|
+
|
8
|
+
package_path = PackageBuilder.new.generate(apple_id: config[:apple_id],
|
9
|
+
ipa_path: config[:ipa],
|
10
|
+
package_path: "/tmp") # TODO: Config
|
11
|
+
|
12
|
+
result = FastlaneCore::ItunesTransporter.new.upload(config[:apple_id], package_path)
|
13
|
+
if result
|
14
|
+
Helper.log.info "Successfully uploaded the new binary to iTunes Connect"
|
15
|
+
|
16
|
+
unless config[:skip_submission]
|
17
|
+
upload_date = wait_for_processing_build
|
18
|
+
distribute_build(upload_date)
|
19
|
+
|
20
|
+
Helper.log.info "Successfully distribute build to beta testers 🚀"
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise "Error uploading ipa file, more information see above".red
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def list(options)
|
28
|
+
start(options)
|
29
|
+
if config[:apple_id].to_s.length == 0 and config[:app_identifier].to_s.length == 0
|
30
|
+
config[:app_identifier] = ask("App Identifier: ")
|
31
|
+
end
|
32
|
+
|
33
|
+
rows = app.all_processing_builds.collect { |build| describe_build(build) }
|
34
|
+
rows = rows + app.builds.collect { |build| describe_build(build) }
|
35
|
+
|
36
|
+
puts Terminal::Table.new(
|
37
|
+
title: "#{app.name} Builds".green,
|
38
|
+
headings: ["Version #", "Build #", "Testing", "Installs", "Sessions"],
|
39
|
+
rows: rows
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
def describe_build(build)
|
45
|
+
row = [build.train_version,
|
46
|
+
build.build_version,
|
47
|
+
build.testing_status,
|
48
|
+
build.install_count,
|
49
|
+
build.session_count]
|
50
|
+
|
51
|
+
return row
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method will takes care of checking for the processing builds every few seconds
|
55
|
+
# @return [Integer] The upload date
|
56
|
+
def wait_for_processing_build
|
57
|
+
# the upload date of the new buid
|
58
|
+
# we use it to identify the build
|
59
|
+
upload_date = nil
|
60
|
+
loop do
|
61
|
+
Helper.log.info "Waiting for iTunes Connect to process the new build"
|
62
|
+
sleep 5
|
63
|
+
builds = app.all_processing_builds
|
64
|
+
break if builds.count == 0
|
65
|
+
upload_date = builds.last.upload_date
|
66
|
+
end
|
67
|
+
|
68
|
+
if upload_date
|
69
|
+
Helper.log.info "Build successfully processed by iTunes Connect".green
|
70
|
+
return upload_date
|
71
|
+
else
|
72
|
+
raise "Error: Seems like iTunes Connect didn't properly pre-process the binary".red
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def distribute_build(upload_date)
|
77
|
+
Helper.log.info "Distributing new build to testers"
|
78
|
+
|
79
|
+
current_build = app.builds.find do |build|
|
80
|
+
build.upload_date == upload_date
|
81
|
+
end
|
82
|
+
|
83
|
+
# First, enable TestFlight beta testing for this train
|
84
|
+
current_build.build_train.update_testing_status!(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require "commander"
|
2
|
+
require "pilot/options"
|
3
|
+
require "fastlane_core"
|
4
|
+
|
5
|
+
HighLine.track_eof = false
|
6
|
+
|
7
|
+
module Pilot
|
8
|
+
class CommandsGenerator
|
9
|
+
include Commander::Methods
|
10
|
+
|
11
|
+
FastlaneCore::CommanderGenerator.new.generate(Pilot::Options.available_options)
|
12
|
+
|
13
|
+
def self.start
|
14
|
+
FastlaneCore::UpdateChecker.start_looking_for_update("pilot")
|
15
|
+
new.run
|
16
|
+
ensure
|
17
|
+
FastlaneCore::UpdateChecker.show_update_status("pilot", Pilot::VERSION)
|
18
|
+
end
|
19
|
+
|
20
|
+
def convert_options(options)
|
21
|
+
o = options.__hash__.dup
|
22
|
+
o.delete(:verbose)
|
23
|
+
o
|
24
|
+
end
|
25
|
+
|
26
|
+
def handle_email(config, _args)
|
27
|
+
config[:email] ||= _args.first
|
28
|
+
config[:email] ||= ask("Email address of the tester: ".yellow)
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
program :version, Pilot::VERSION
|
33
|
+
program :description, Pilot::DESCRIPTION
|
34
|
+
program :help, "Author", "Felix Krause <pilot@krausefx.com>"
|
35
|
+
program :help, "Website", "https://fastlane.tools"
|
36
|
+
program :help, "GitHub", "https://github.com/fastlane/pilot"
|
37
|
+
program :help_formatter, :compact
|
38
|
+
|
39
|
+
global_option("--verbose") { $verbose = true }
|
40
|
+
|
41
|
+
always_trace!
|
42
|
+
|
43
|
+
command :upload do |c|
|
44
|
+
c.syntax = "pilot upload"
|
45
|
+
c.description = "Uploads a new binary to Apple TestFlight"
|
46
|
+
c.action do |_args, options|
|
47
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
48
|
+
Pilot::BuildManager.new.upload(config)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
command :builds do |c|
|
53
|
+
c.syntax = "pilot builds"
|
54
|
+
c.description = "Lists all builds for given application"
|
55
|
+
c.action do |_args, options|
|
56
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
57
|
+
Pilot::BuildManager.new.list(config)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
command :add do |c|
|
62
|
+
c.syntax = "pilot add"
|
63
|
+
c.description = "Adds a new external tester to a specific app (if given). This will also add an existing tester to an app."
|
64
|
+
c.action do |_args, options|
|
65
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
66
|
+
handle_email(config, _args)
|
67
|
+
Pilot::TesterManager.new.add_tester(config)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
command :list do |c|
|
72
|
+
c.syntax = "pilot list"
|
73
|
+
c.description = "Lists all registered testers, both internal and external"
|
74
|
+
c.action do |_args, options|
|
75
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
76
|
+
Pilot::TesterManager.new.list_testers(config)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
command :find do |c|
|
81
|
+
c.syntax = "pilot find"
|
82
|
+
c.description = "Find a tester (internal or external) by their email address"
|
83
|
+
c.action do |_args, options|
|
84
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
85
|
+
handle_email(config, _args)
|
86
|
+
Pilot::TesterManager.new.find_tester(config)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
command :remove do |c|
|
91
|
+
c.syntax = "pilot remove"
|
92
|
+
c.description = "Remove an external tester by their email address"
|
93
|
+
c.action do |_args, options|
|
94
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
95
|
+
handle_email(config, _args)
|
96
|
+
Pilot::TesterManager.new.remove_tester(config)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
command :export do |c|
|
101
|
+
c.syntax = "pilot export"
|
102
|
+
c.description = "Exports all external testers to a CSV file"
|
103
|
+
c.action do |_args, options|
|
104
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
105
|
+
Pilot::TesterExporter.new.export_testers(config)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
command :import do |c|
|
110
|
+
c.syntax = "pilot import"
|
111
|
+
c.description = "Create external testers from a CSV file"
|
112
|
+
c.action do |_args, options|
|
113
|
+
config = FastlaneCore::Configuration.create(Pilot::Options.available_options, convert_options(options))
|
114
|
+
Pilot::TesterImporter.new.import_testers(config)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
default_command :help
|
119
|
+
|
120
|
+
run!
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "fastlane_core"
|
2
|
+
|
3
|
+
module Pilot
|
4
|
+
class Manager
|
5
|
+
def start(options)
|
6
|
+
return if @config # to not login multiple times
|
7
|
+
@config = options
|
8
|
+
login
|
9
|
+
end
|
10
|
+
|
11
|
+
def login
|
12
|
+
user = config[:username]
|
13
|
+
user ||= ENV["DELIVER_USERNAME"]
|
14
|
+
user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id)
|
15
|
+
CredentialsManager::PasswordManager.shared_manager(user) if user
|
16
|
+
|
17
|
+
Helper.log.info "Login to iTunes Connect"
|
18
|
+
Spaceship::Tunes.login(user, CredentialsManager::PasswordManager.shared_manager(user).password)
|
19
|
+
Helper.log.info "Login successful"
|
20
|
+
end
|
21
|
+
|
22
|
+
# The app object we're currently using
|
23
|
+
def app
|
24
|
+
@apple_id ||= fetch_app_id
|
25
|
+
|
26
|
+
unless (@app ||= Spaceship::Application.find(@apple_id))
|
27
|
+
raise "Could not find app with #{(config[:apple_id] || config[:app_identifier])}"
|
28
|
+
end
|
29
|
+
return @app
|
30
|
+
end
|
31
|
+
|
32
|
+
# Access the current configuration
|
33
|
+
def config
|
34
|
+
@config
|
35
|
+
end
|
36
|
+
|
37
|
+
# Config Related
|
38
|
+
################
|
39
|
+
|
40
|
+
def fetch_app_id
|
41
|
+
return @apple_id if @apple_id
|
42
|
+
config[:app_identifier] = fetch_app_identifier
|
43
|
+
|
44
|
+
if config[:app_identifier]
|
45
|
+
@app ||= Spaceship::Application.find(config[:app_identifier])
|
46
|
+
app_id ||= @app.apple_id
|
47
|
+
end
|
48
|
+
|
49
|
+
app_id ||= ask("Could not automatically find the app ID, please enter it here (e.g. 956814360): ")
|
50
|
+
|
51
|
+
return app_id
|
52
|
+
end
|
53
|
+
|
54
|
+
def fetch_app_identifier
|
55
|
+
result = config[:app_identifier]
|
56
|
+
result ||= FastlaneCore::IpaFileAnalyser.fetch_app_identifier(config[:ipa])
|
57
|
+
result ||= ask("Please enter the app's bundle identifier: ")
|
58
|
+
return result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require "fastlane_core"
|
2
|
+
require "credentials_manager"
|
3
|
+
|
4
|
+
module Pilot
|
5
|
+
class Options
|
6
|
+
def self.available_options
|
7
|
+
@@options ||= [
|
8
|
+
FastlaneCore::ConfigItem.new(key: :username,
|
9
|
+
short_option: "-u",
|
10
|
+
env_name: "PILOT_USERNAME",
|
11
|
+
description: "Your Apple ID Username",
|
12
|
+
default_value: ENV["DELIVER_USER"] || CredentialsManager::AppfileConfig.try_fetch_value(:apple_id),
|
13
|
+
verify_block: proc do |value|
|
14
|
+
CredentialsManager::PasswordManager.shared_manager(value)
|
15
|
+
end),
|
16
|
+
|
17
|
+
FastlaneCore::ConfigItem.new(key: :ipa,
|
18
|
+
short_option: "-i",
|
19
|
+
env_name: "PILOT_IPA",
|
20
|
+
description: "Path to the ipa file to upload",
|
21
|
+
default_value: Dir["*.ipa"].first,
|
22
|
+
verify_block: proc do |value|
|
23
|
+
fail "Could not find ipa file at path '#{value}'" unless File.exist? value
|
24
|
+
fail "'#{value}' doesn't seem to be an ipa file" unless value.end_with? ".ipa"
|
25
|
+
end),
|
26
|
+
FastlaneCore::ConfigItem.new(key: :skip_submission,
|
27
|
+
short_option: "-s",
|
28
|
+
env_name: "PILOT_SKIP_SUBMISSION",
|
29
|
+
description: "Skip the distributing action of pilot and only upload the ipa file",
|
30
|
+
default_value: false),
|
31
|
+
FastlaneCore::ConfigItem.new(key: :app_identifier,
|
32
|
+
short_option: "-a",
|
33
|
+
env_name: "PILOT_APP_IDENTIFIER",
|
34
|
+
description: "The bundle identifier of the app to upload or manage testers (optional)",
|
35
|
+
optional: true,
|
36
|
+
default_value: ENV["TESTFLIGHT_APP_IDENTITIFER"] || CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier),
|
37
|
+
verify_block: proc do |_value|
|
38
|
+
end),
|
39
|
+
FastlaneCore::ConfigItem.new(key: :apple_id,
|
40
|
+
short_option: "-p",
|
41
|
+
env_name: "PILOT_APPLE_ID",
|
42
|
+
description: "The unique App ID provided by iTunes Connect",
|
43
|
+
optional: true,
|
44
|
+
default_value: ENV["TESTFLIGHT_APPLE_ID"],
|
45
|
+
verify_block: proc do |_value|
|
46
|
+
end),
|
47
|
+
FastlaneCore::ConfigItem.new(key: :first_name,
|
48
|
+
short_option: "-f",
|
49
|
+
env_name: "PILOT_TESTER_FIRST_NAME",
|
50
|
+
description: "The tester's first name",
|
51
|
+
optional: true,
|
52
|
+
verify_block: proc do |_value|
|
53
|
+
end),
|
54
|
+
FastlaneCore::ConfigItem.new(key: :last_name,
|
55
|
+
short_option: "-l",
|
56
|
+
env_name: "PILOT_TESTER_LAST_NAME",
|
57
|
+
description: "The tester's last name",
|
58
|
+
optional: true,
|
59
|
+
verify_block: proc do |_value|
|
60
|
+
end),
|
61
|
+
FastlaneCore::ConfigItem.new(key: :email,
|
62
|
+
short_option: "-e",
|
63
|
+
env_name: "PILOT_TESTER_EMAIL",
|
64
|
+
description: "The tester's email",
|
65
|
+
optional: true,
|
66
|
+
verify_block: proc do |_value|
|
67
|
+
raise "Please pass a valid email address" unless _value.include?"@"
|
68
|
+
end),
|
69
|
+
FastlaneCore::ConfigItem.new(key: :group_name,
|
70
|
+
short_option: "-g",
|
71
|
+
env_name: "PILOT_TESTER_GROUP",
|
72
|
+
description: "Group to add the tester to",
|
73
|
+
optional: true,
|
74
|
+
verify_block: proc do |_value|
|
75
|
+
end),
|
76
|
+
FastlaneCore::ConfigItem.new(key: :testers_file_path,
|
77
|
+
short_option: "-c",
|
78
|
+
env_name: "PILOT_TESTERS_FILE",
|
79
|
+
description: "Path to a CSV file of testers",
|
80
|
+
default_value: "./testers.csv",
|
81
|
+
optional: true,
|
82
|
+
verify_block: proc do |_value|
|
83
|
+
end)
|
84
|
+
|
85
|
+
]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "digest/md5"
|
2
|
+
|
3
|
+
module Pilot
|
4
|
+
class PackageBuilder
|
5
|
+
METADATA_FILE_NAME = "metadata.xml"
|
6
|
+
|
7
|
+
attr_accessor :package_path
|
8
|
+
|
9
|
+
def generate(apple_id: nil, ipa_path: nil, package_path: nil)
|
10
|
+
self.package_path = File.join(package_path, "#{apple_id}.itmsp")
|
11
|
+
FileUtils.rm_rf self.package_path rescue nil
|
12
|
+
FileUtils.mkdir_p self.package_path
|
13
|
+
|
14
|
+
lib_path = Helper.gem_path("pilot")
|
15
|
+
|
16
|
+
ipa_path = copy_ipa(ipa_path)
|
17
|
+
@data = {
|
18
|
+
apple_id: apple_id,
|
19
|
+
file_size: File.size(ipa_path),
|
20
|
+
ipa_path: File.basename(ipa_path), # this is only the base name as the ipa is inside the package
|
21
|
+
md5: Digest::MD5.hexdigest(File.read(ipa_path))
|
22
|
+
}
|
23
|
+
|
24
|
+
xml_path = File.join(lib_path, "lib/assets/XMLTemplate.xml.erb")
|
25
|
+
xml = ERB.new(File.read(xml_path)).result(binding) # http://www.rrn.dk/rubys-erb-templating-system
|
26
|
+
|
27
|
+
File.write(File.join(self.package_path, METADATA_FILE_NAME), xml)
|
28
|
+
Helper.log.info "Wrote XML data to '#{self.package_path}'".green if $verbose
|
29
|
+
|
30
|
+
return package_path
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def copy_ipa(ipa_path)
|
35
|
+
ipa_file_name = Digest::MD5.hexdigest(ipa_path)
|
36
|
+
resulting_path = File.join(self.package_path, "#{ipa_file_name}.ipa")
|
37
|
+
FileUtils.cp(ipa_path, resulting_path)
|
38
|
+
|
39
|
+
return resulting_path
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "fastlane_core"
|
2
|
+
|
3
|
+
module Pilot
|
4
|
+
class TesterExporter < Manager
|
5
|
+
def export_testers(options)
|
6
|
+
|
7
|
+
raise "Export file path is required".red unless options[:testers_file_path]
|
8
|
+
|
9
|
+
start(options)
|
10
|
+
require 'csv'
|
11
|
+
|
12
|
+
testers = Spaceship::Tunes::Tester::External.all
|
13
|
+
|
14
|
+
file = config[:testers_file_path]
|
15
|
+
|
16
|
+
CSV.open(file, "w") do |csv|
|
17
|
+
|
18
|
+
csv << ['First', 'Last', 'Email', 'Devices', 'Groups', 'Installed Version', 'Install Date']
|
19
|
+
|
20
|
+
testers.each do |tester|
|
21
|
+
|
22
|
+
groups = tester.raw_data.get("groups")
|
23
|
+
|
24
|
+
group_names = ""
|
25
|
+
if groups && groups.length > 0
|
26
|
+
names = groups.map { |group| group["name"]["value"] }
|
27
|
+
group_names = names.join(';')
|
28
|
+
end
|
29
|
+
|
30
|
+
csv << [tester.first_name, tester.last_name, tester.email, tester.devices.count, group_names]
|
31
|
+
end
|
32
|
+
|
33
|
+
Helper.log.info "Successfully exported CSV to #{file}".green
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "fastlane_core"
|
2
|
+
|
3
|
+
module Pilot
|
4
|
+
class TesterImporter < Manager
|
5
|
+
def import_testers(options)
|
6
|
+
raise "Import file path is required".red unless options[:testers_file_path]
|
7
|
+
|
8
|
+
start(options)
|
9
|
+
|
10
|
+
require 'csv'
|
11
|
+
|
12
|
+
file = config[:testers_file_path]
|
13
|
+
tester_manager = Pilot::TesterManager.new
|
14
|
+
imported_tester_count = 0
|
15
|
+
|
16
|
+
is_first = true
|
17
|
+
CSV.foreach(file, "r") do |row|
|
18
|
+
if is_first
|
19
|
+
is_first = false
|
20
|
+
next
|
21
|
+
end
|
22
|
+
|
23
|
+
first_name, last_name, email = row
|
24
|
+
|
25
|
+
unless email
|
26
|
+
Helper.log.error "No email found in row: #{row}".red
|
27
|
+
next
|
28
|
+
end
|
29
|
+
|
30
|
+
# Add this the existing config hash to pass it to the TesterManager
|
31
|
+
config[:first_name] = first_name
|
32
|
+
config[:last_name] = last_name
|
33
|
+
config[:email] = email
|
34
|
+
|
35
|
+
begin
|
36
|
+
tester_manager.add_tester(config)
|
37
|
+
imported_tester_count += 1
|
38
|
+
rescue => ex
|
39
|
+
# do nothing, move on to the next row
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
Helper.log.info "Successfully imported #{imported_tester_count} testers from #{file}".green
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "fastlane_core"
|
2
|
+
|
3
|
+
module Pilot
|
4
|
+
class TesterManager < Manager
|
5
|
+
def add_tester(options)
|
6
|
+
start(options)
|
7
|
+
|
8
|
+
begin
|
9
|
+
tester = Spaceship::Tunes::Tester::Internal.find(config[:email])
|
10
|
+
tester ||= Spaceship::Tunes::Tester::External.find(config[:email])
|
11
|
+
|
12
|
+
if tester
|
13
|
+
Helper.log.info "Existing tester #{tester.email}".green
|
14
|
+
else
|
15
|
+
tester = Spaceship::Tunes::Tester::External.create!(email: config[:email],
|
16
|
+
first_name: config[:first_name],
|
17
|
+
last_name: config[:last_name],
|
18
|
+
group: config[:group_name])
|
19
|
+
Helper.log.info "Successfully invited tester: #{tester.email}".green
|
20
|
+
end
|
21
|
+
|
22
|
+
app_filter = (config[:apple_id] || config[:app_identifier])
|
23
|
+
if app_filter
|
24
|
+
begin
|
25
|
+
app = Spaceship::Application.find(app_filter)
|
26
|
+
raise "Couldn't find app with '#{app_filter}'" unless app
|
27
|
+
tester.add_to_app!(app.apple_id)
|
28
|
+
Helper.log.info "Successfully added tester to app #{app_filter}".green
|
29
|
+
rescue => ex
|
30
|
+
Helper.log.error "Could not add #{tester.email} to app: #{ex}".red
|
31
|
+
raise ex
|
32
|
+
end
|
33
|
+
end
|
34
|
+
rescue => ex
|
35
|
+
Helper.log.error "Could not create tester #{config[:email]}".red
|
36
|
+
raise ex
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_tester(options)
|
41
|
+
start(options)
|
42
|
+
|
43
|
+
tester = Spaceship::Tunes::Tester::Internal.find(config[:email])
|
44
|
+
tester ||= Spaceship::Tunes::Tester::External.find(config[:email])
|
45
|
+
|
46
|
+
raise "Tester #{config[:email]} not found".red unless tester
|
47
|
+
|
48
|
+
describe_tester(tester)
|
49
|
+
return tester
|
50
|
+
end
|
51
|
+
|
52
|
+
def remove_tester(options)
|
53
|
+
start(options)
|
54
|
+
|
55
|
+
tester = Spaceship::Tunes::Tester::External.find(config[:email])
|
56
|
+
tester ||= Spaceship::Tunes::Tester::Internal.find(config[:email])
|
57
|
+
|
58
|
+
if tester
|
59
|
+
tester.delete!
|
60
|
+
Helper.log.info "Successully removed tester #{tester.email}".green
|
61
|
+
else
|
62
|
+
Helper.log.error "Tester not found: #{config[:email]}".red
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def list_testers(options)
|
67
|
+
start(options)
|
68
|
+
require 'terminal-table'
|
69
|
+
|
70
|
+
list(Spaceship::Tunes::Tester::Internal.all, "Internal Testers")
|
71
|
+
puts "" # new line
|
72
|
+
list(Spaceship::Tunes::Tester::External.all, "External Testers")
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def list(all_testers, title)
|
78
|
+
rows = []
|
79
|
+
all_testers.each do |tester|
|
80
|
+
rows << [tester.first_name, tester.last_name, tester.email, tester.devices.count]
|
81
|
+
end
|
82
|
+
|
83
|
+
puts Terminal::Table.new(
|
84
|
+
title: title.green,
|
85
|
+
headings: ["First", "Last", "Email", "Devices"],
|
86
|
+
rows: rows
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Print out all the details of a specific tester
|
91
|
+
def describe_tester(tester)
|
92
|
+
return unless tester
|
93
|
+
|
94
|
+
rows = []
|
95
|
+
|
96
|
+
rows << ["First name", tester.first_name]
|
97
|
+
rows << ["Last name", tester.last_name]
|
98
|
+
rows << ["Email", tester.email]
|
99
|
+
|
100
|
+
groups = tester.raw_data.get("groups")
|
101
|
+
|
102
|
+
if groups && groups.length > 0
|
103
|
+
group_names = groups.map { |group| group["name"]["value"] }
|
104
|
+
rows << ["Groups", group_names.join(', ')]
|
105
|
+
end
|
106
|
+
|
107
|
+
if tester.latest_install_date
|
108
|
+
latest_installed_version = tester.latest_installed_version_number
|
109
|
+
latest_installed_short_version = tester.latest_installed_build_number
|
110
|
+
pretty_date = Time.at((tester.latest_install_date / 1000)).strftime("%m/%d/%y %H:%M")
|
111
|
+
|
112
|
+
rows << ["Latest Version", "#{latest_installed_version} (#{latest_installed_short_version})"]
|
113
|
+
rows << ["Latest Install Date", pretty_date]
|
114
|
+
end
|
115
|
+
|
116
|
+
if tester.devices.length == 0
|
117
|
+
rows << ["Devices", "No devices"]
|
118
|
+
else
|
119
|
+
rows << ["#{tester.devices.count} Devices", ""]
|
120
|
+
tester.devices.each do |device|
|
121
|
+
current = "\u2022 #{device['model']}, iOS #{device['osVersion']}"
|
122
|
+
|
123
|
+
if rows.last[1].length == 0
|
124
|
+
rows.last[1] = current
|
125
|
+
else
|
126
|
+
rows << ["", current]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
puts Terminal::Table.new(
|
132
|
+
title: tester.email.green,
|
133
|
+
rows: rows
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/pilot/version.rb
CHANGED
metadata
CHANGED
@@ -1,142 +1,170 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pilot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Felix Krause
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: fastlane_core
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 0.
|
19
|
+
version: 0.9.2
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 0.
|
26
|
+
version: 0.9.2
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: spaceship
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.3.0
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.3.0
|
27
41
|
- !ruby/object:Gem::Dependency
|
28
42
|
name: credentials_manager
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
30
44
|
requirements:
|
31
|
-
- -
|
45
|
+
- - ">="
|
32
46
|
- !ruby/object:Gem::Version
|
33
47
|
version: 0.3.0
|
34
48
|
type: :runtime
|
35
49
|
prerelease: false
|
36
50
|
version_requirements: !ruby/object:Gem::Requirement
|
37
51
|
requirements:
|
38
|
-
- -
|
52
|
+
- - ">="
|
39
53
|
- !ruby/object:Gem::Version
|
40
54
|
version: 0.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: terminal-table
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 1.4.5
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 1.4.5
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: bundler
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
44
72
|
requirements:
|
45
|
-
- -
|
73
|
+
- - ">="
|
46
74
|
- !ruby/object:Gem::Version
|
47
75
|
version: '0'
|
48
76
|
type: :development
|
49
77
|
prerelease: false
|
50
78
|
version_requirements: !ruby/object:Gem::Requirement
|
51
79
|
requirements:
|
52
|
-
- -
|
80
|
+
- - ">="
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '0'
|
55
83
|
- !ruby/object:Gem::Dependency
|
56
84
|
name: rake
|
57
85
|
requirement: !ruby/object:Gem::Requirement
|
58
86
|
requirements:
|
59
|
-
- -
|
87
|
+
- - ">="
|
60
88
|
- !ruby/object:Gem::Version
|
61
89
|
version: '0'
|
62
90
|
type: :development
|
63
91
|
prerelease: false
|
64
92
|
version_requirements: !ruby/object:Gem::Requirement
|
65
93
|
requirements:
|
66
|
-
- -
|
94
|
+
- - ">="
|
67
95
|
- !ruby/object:Gem::Version
|
68
96
|
version: '0'
|
69
97
|
- !ruby/object:Gem::Dependency
|
70
98
|
name: rspec
|
71
99
|
requirement: !ruby/object:Gem::Requirement
|
72
100
|
requirements:
|
73
|
-
- - ~>
|
101
|
+
- - "~>"
|
74
102
|
- !ruby/object:Gem::Version
|
75
103
|
version: 3.1.0
|
76
104
|
type: :development
|
77
105
|
prerelease: false
|
78
106
|
version_requirements: !ruby/object:Gem::Requirement
|
79
107
|
requirements:
|
80
|
-
- - ~>
|
108
|
+
- - "~>"
|
81
109
|
- !ruby/object:Gem::Version
|
82
110
|
version: 3.1.0
|
83
111
|
- !ruby/object:Gem::Dependency
|
84
112
|
name: pry
|
85
113
|
requirement: !ruby/object:Gem::Requirement
|
86
114
|
requirements:
|
87
|
-
- -
|
115
|
+
- - ">="
|
88
116
|
- !ruby/object:Gem::Version
|
89
117
|
version: '0'
|
90
118
|
type: :development
|
91
119
|
prerelease: false
|
92
120
|
version_requirements: !ruby/object:Gem::Requirement
|
93
121
|
requirements:
|
94
|
-
- -
|
122
|
+
- - ">="
|
95
123
|
- !ruby/object:Gem::Version
|
96
124
|
version: '0'
|
97
125
|
- !ruby/object:Gem::Dependency
|
98
126
|
name: yard
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
|
-
- - ~>
|
129
|
+
- - "~>"
|
102
130
|
- !ruby/object:Gem::Version
|
103
131
|
version: 0.8.7.4
|
104
132
|
type: :development
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
|
-
- - ~>
|
136
|
+
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
138
|
version: 0.8.7.4
|
111
139
|
- !ruby/object:Gem::Dependency
|
112
140
|
name: webmock
|
113
141
|
requirement: !ruby/object:Gem::Requirement
|
114
142
|
requirements:
|
115
|
-
- - ~>
|
143
|
+
- - "~>"
|
116
144
|
- !ruby/object:Gem::Version
|
117
145
|
version: 1.19.0
|
118
146
|
type: :development
|
119
147
|
prerelease: false
|
120
148
|
version_requirements: !ruby/object:Gem::Requirement
|
121
149
|
requirements:
|
122
|
-
- - ~>
|
150
|
+
- - "~>"
|
123
151
|
- !ruby/object:Gem::Version
|
124
152
|
version: 1.19.0
|
125
153
|
- !ruby/object:Gem::Dependency
|
126
154
|
name: coveralls
|
127
155
|
requirement: !ruby/object:Gem::Requirement
|
128
156
|
requirements:
|
129
|
-
- -
|
157
|
+
- - ">="
|
130
158
|
- !ruby/object:Gem::Version
|
131
159
|
version: '0'
|
132
160
|
type: :development
|
133
161
|
prerelease: false
|
134
162
|
version_requirements: !ruby/object:Gem::Requirement
|
135
163
|
requirements:
|
136
|
-
- -
|
164
|
+
- - ">="
|
137
165
|
- !ruby/object:Gem::Version
|
138
166
|
version: '0'
|
139
|
-
description: The
|
167
|
+
description: The best way to manage your TestFlight testers and builds from your terminal
|
140
168
|
email:
|
141
169
|
- pilot@krausefx.com
|
142
170
|
executables:
|
@@ -147,7 +175,16 @@ files:
|
|
147
175
|
- LICENSE
|
148
176
|
- README.md
|
149
177
|
- bin/pilot
|
178
|
+
- lib/assets/XMLTemplate.xml.erb
|
150
179
|
- lib/pilot.rb
|
180
|
+
- lib/pilot/build_manager.rb
|
181
|
+
- lib/pilot/commands_generator.rb
|
182
|
+
- lib/pilot/manager.rb
|
183
|
+
- lib/pilot/options.rb
|
184
|
+
- lib/pilot/package_builder.rb
|
185
|
+
- lib/pilot/tester_exporter.rb
|
186
|
+
- lib/pilot/tester_importer.rb
|
187
|
+
- lib/pilot/tester_manager.rb
|
151
188
|
- lib/pilot/version.rb
|
152
189
|
homepage: https://fastlane.tools
|
153
190
|
licenses:
|
@@ -159,19 +196,19 @@ require_paths:
|
|
159
196
|
- lib
|
160
197
|
required_ruby_version: !ruby/object:Gem::Requirement
|
161
198
|
requirements:
|
162
|
-
- -
|
199
|
+
- - ">="
|
163
200
|
- !ruby/object:Gem::Version
|
164
201
|
version: 2.0.0
|
165
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
166
203
|
requirements:
|
167
|
-
- -
|
204
|
+
- - ">="
|
168
205
|
- !ruby/object:Gem::Version
|
169
206
|
version: '0'
|
170
207
|
requirements: []
|
171
208
|
rubyforge_project:
|
172
|
-
rubygems_version: 2.4.
|
209
|
+
rubygems_version: 2.4.6
|
173
210
|
signing_key:
|
174
211
|
specification_version: 4
|
175
|
-
summary: The
|
212
|
+
summary: The best way to manage your TestFlight testers and builds from your terminal
|
176
213
|
test_files: []
|
177
214
|
has_rdoc:
|