tiny_appstore_connect 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +11 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +39 -0
- data/lib/tiny_appstore_connect/client.rb +95 -0
- data/lib/tiny_appstore_connect/clients/app.rb +45 -0
- data/lib/tiny_appstore_connect/clients/app_store_version.rb +27 -0
- data/lib/tiny_appstore_connect/clients/build.rb +28 -0
- data/lib/tiny_appstore_connect/clients/device.rb +18 -0
- data/lib/tiny_appstore_connect/model.rb +169 -0
- data/lib/tiny_appstore_connect/models/app.rb +28 -0
- data/lib/tiny_appstore_connect/models/app_store_version.rb +85 -0
- data/lib/tiny_appstore_connect/models/app_store_version_submission.rb +16 -0
- data/lib/tiny_appstore_connect/models/build.rb +31 -0
- data/lib/tiny_appstore_connect/models/device.rb +45 -0
- data/lib/tiny_appstore_connect/models/pre_release_version.rb +17 -0
- data/lib/tiny_appstore_connect/response.rb +103 -0
- data/lib/tiny_appstore_connect/token.rb +56 -0
- data/lib/tiny_appstore_connect/version.rb +5 -0
- data/lib/tiny_appstore_connect.rb +53 -0
- data/tiny_appstore_connect.gemspec +32 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4c0a0ee36290dce8d6fec8d64801bda09fa15e27668d755e058739a1bdc04d79
|
4
|
+
data.tar.gz: 2d5b81ef78cd0c3a004df7e625044f01761582b87f3ce5e244a168cc9b3c6283
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 614467c40dfdc1e23aebc2fd823a0becb210abc6aaebeaeafea348f98826431f408a2476adfc39ab319f561d779838527ab8c02607fec6c7ba5c30c4dcc774b2
|
7
|
+
data.tar.gz: b8380dfa0bf0e96c580b55cd5651837cf5767f2b28362f2bc554c181d4d0e3860f5be71f5fea8968e9c0b092b733d62ec404538716e17c241df81d2936ade76c
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 icyleaf
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Tiny Appstore Connect
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/appstore_connect`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'tiny_appstore_connect'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install tiny_appstore_connect
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/appstore_connect.
|
36
|
+
|
37
|
+
## License
|
38
|
+
|
39
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bundler/gem_tasks"
|
4
|
+
require "rspec/core/rake_task"
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
|
8
|
+
require 'rubocop/rake_task'
|
9
|
+
|
10
|
+
RuboCop::RakeTask.new
|
11
|
+
|
12
|
+
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
|
15
|
+
namespace :github do
|
16
|
+
task :release do
|
17
|
+
Rake::Task['build'].invoke
|
18
|
+
Rake::Task['release:guard_clean'].invoke
|
19
|
+
Rake::Task['release:source_control_push'].invoke
|
20
|
+
Rake::Task['github:push'].invoke
|
21
|
+
end
|
22
|
+
|
23
|
+
task :push do
|
24
|
+
helper = Bundler::GemHelper.new
|
25
|
+
# puts helper.base
|
26
|
+
# puts helper.gemspec
|
27
|
+
puts "#{helper.send(:name)}-*.gem"
|
28
|
+
puts helper.send(:name)
|
29
|
+
puts helper.send(:built_gem_path)
|
30
|
+
puts Gem::Util.glob_files_in_dir("#{helper.send(:name)}-*.gem", helper.base)
|
31
|
+
# puts helper.send(:rubygem_push, helper.send(:built_gem_path))
|
32
|
+
# helper.send(:rubygem_push, '.')
|
33
|
+
# cmd = [*gem_command, "push", path]
|
34
|
+
# cmd << "--key" << gem_key if gem_key
|
35
|
+
# cmd << "--host" << allowed_push_host if allowed_push_host
|
36
|
+
|
37
|
+
# Bundler.ui.debug(cmd)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
require 'faraday'
|
7
|
+
if Gem::Dependency.new('', '< 2.0.0').match?('', Faraday::VERSION)
|
8
|
+
require 'faraday_middleware'
|
9
|
+
end
|
10
|
+
|
11
|
+
module TinyAppstoreConnect
|
12
|
+
class Client
|
13
|
+
Dir[File.expand_path('clients/*.rb', __dir__)].each { |f| require f }
|
14
|
+
|
15
|
+
ENDPOINT = 'https://api.appstoreconnect.apple.com/v1'
|
16
|
+
|
17
|
+
include App
|
18
|
+
include AppStoreVersion
|
19
|
+
include Build
|
20
|
+
include Device
|
21
|
+
|
22
|
+
attr_reader :connection
|
23
|
+
|
24
|
+
def initialize(**kargs)
|
25
|
+
configure_connection(**kargs)
|
26
|
+
end
|
27
|
+
|
28
|
+
%w[get post patch delete].each do |method|
|
29
|
+
define_method method do |path, options = {}|
|
30
|
+
params = options.dup
|
31
|
+
|
32
|
+
if %w[post patch].include?(method)
|
33
|
+
body = params[:body].to_json
|
34
|
+
headers = params[:headers] || {}
|
35
|
+
headers[:content_type] ||= 'application/json'
|
36
|
+
validates connection.send(method, path, body, headers)
|
37
|
+
else
|
38
|
+
validates connection.send(method, path, params)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def validates(response)
|
46
|
+
puts response.body
|
47
|
+
case response.status
|
48
|
+
when 200, 201, 204
|
49
|
+
# 200: get requests
|
50
|
+
# 201: post requests
|
51
|
+
# 204: patch, delete requests
|
52
|
+
handle_response(response)
|
53
|
+
else
|
54
|
+
raise ConnectAPIError.parse(response)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def handle_response(response)
|
59
|
+
response = Response.new(response, connection)
|
60
|
+
|
61
|
+
if (remaining = response.rate[:remaining]) && remaining.to_i.zero?
|
62
|
+
raise RateLimitExceededError, "Request limit reached #{response.rate[:limit]}
|
63
|
+
in the previous 60 minutes with url: #{response.request_url}"
|
64
|
+
end
|
65
|
+
|
66
|
+
response
|
67
|
+
end
|
68
|
+
|
69
|
+
def configure_connection(**kargs)
|
70
|
+
@token = Token.new(**kargs)
|
71
|
+
endpoint = kargs[:endpoint] || ENDPOINT
|
72
|
+
|
73
|
+
connection_opts= {
|
74
|
+
request: {
|
75
|
+
timeout: (kargs[:timeout] || 300).to_i,
|
76
|
+
open_timeout: (kargs[:timeout] || 300).to_i
|
77
|
+
}
|
78
|
+
}
|
79
|
+
|
80
|
+
@connection = Faraday.new(endpoint) do |builder|
|
81
|
+
builder.headers[:user_agent] = "TinyAppstoreConnect v#{TinyAppstoreConnect::VERSION}"
|
82
|
+
builder.headers[:accept] = 'application/json'
|
83
|
+
builder.request :url_encoded
|
84
|
+
builder.request :authorization, 'Bearer', -> { @token.token }
|
85
|
+
builder.response :json, content_type: /\bjson$/
|
86
|
+
|
87
|
+
if kargs[:debug] || ENV['ASC_DEBUG']
|
88
|
+
builder.proxy = kargs[:proxy] || ENV['ASC_PROXY'] ||'https://127.0.0.1:8888'
|
89
|
+
builder.ssl[:verify_mode] = OpenSSL::SSL::VERIFY_NONE
|
90
|
+
builder.response :logger, nil, { log_level: :debug }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
class TinyAppstoreConnect::Client
|
7
|
+
module App
|
8
|
+
def apps(query = {})
|
9
|
+
get('apps', query)
|
10
|
+
end
|
11
|
+
|
12
|
+
def app(id, query = {})
|
13
|
+
get("apps/#{id}", query).to_model
|
14
|
+
end
|
15
|
+
|
16
|
+
def app_versions(id, query = {})
|
17
|
+
get("apps/#{id}/appStoreVersions", query)
|
18
|
+
end
|
19
|
+
|
20
|
+
def app_edit_version(id, includes: AppStoreVersion::ESSENTIAL_INCLUDES)
|
21
|
+
filters = {
|
22
|
+
appStoreState: [
|
23
|
+
AppStoreState::PREPARE_FOR_SUBMISSION,
|
24
|
+
AppStoreState::DEVELOPER_REJECTED,
|
25
|
+
AppStoreState::REJECTED,
|
26
|
+
AppStoreState::METADATA_REJECTED,
|
27
|
+
AppStoreState::WAITING_FOR_REVIEW,
|
28
|
+
AppStoreState::INVALID_BINARY,
|
29
|
+
AppStoreState::IN_REVIEW,
|
30
|
+
AppStoreState::PENDING_DEVELOPER_RELEASE
|
31
|
+
].join(',')
|
32
|
+
}
|
33
|
+
|
34
|
+
app_versions(id, include: includes, filter: filters).to_model
|
35
|
+
end
|
36
|
+
|
37
|
+
def app_live_version(id, includes: ESSENTIAL_INCLUDES)
|
38
|
+
filters = {
|
39
|
+
appStoreState: AppStoreState::READY_FOR_SALE
|
40
|
+
}
|
41
|
+
|
42
|
+
app_versions(id, include: includes, filter: filters).to_model
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
class TinyAppstoreConnect::Client
|
7
|
+
module AppStoreVersion
|
8
|
+
# def versions(query = {})
|
9
|
+
# get('appStoreVersions', query)
|
10
|
+
# end
|
11
|
+
|
12
|
+
def version(id, query = {})
|
13
|
+
get("appStoreVersions/#{id}", query)
|
14
|
+
end
|
15
|
+
|
16
|
+
def select_version_build(id, build_id:)
|
17
|
+
body = {
|
18
|
+
data: {
|
19
|
+
type: 'builds',
|
20
|
+
id: build_id
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
patch("appStoreVersions/#{id}/relationships/build", body: body)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
class TinyAppstoreConnect::Client
|
7
|
+
module Build
|
8
|
+
def app_latest_build(id)
|
9
|
+
app_builds(id, limit: 1).to_model
|
10
|
+
end
|
11
|
+
|
12
|
+
def app_builds(id, **kargs)
|
13
|
+
kargs = kargs.merge(filter: { app: id })
|
14
|
+
builds(**kargs)
|
15
|
+
end
|
16
|
+
|
17
|
+
def builds(limit: 200, sort: '-uploadedDate',
|
18
|
+
includes: TinyAppstoreConnect::Model::Build::ESSENTIAL_INCLUDES,
|
19
|
+
**kargs)
|
20
|
+
|
21
|
+
kargs = kargs.merge(limit: limit)
|
22
|
+
.merge(sort: sort)
|
23
|
+
.merge(include: includes)
|
24
|
+
|
25
|
+
get('builds', **kargs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
class TinyAppstoreConnect::Client
|
7
|
+
module Device
|
8
|
+
def devices(filter: {}, includes: nil, limit: nil, sort: nil)
|
9
|
+
kargs = {
|
10
|
+
include: includes,
|
11
|
+
limit: limit,
|
12
|
+
sort: sort
|
13
|
+
}.delete_if { |_, v| v.nil? }
|
14
|
+
|
15
|
+
get('devices', **kargs)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect
|
7
|
+
module Platform
|
8
|
+
IOS = 'IOS'
|
9
|
+
MAC_OS = 'MAC_OS'
|
10
|
+
TV_OS = 'TV_OS'
|
11
|
+
WATCH_OS = 'WATCH_OS'
|
12
|
+
|
13
|
+
ALL = [IOS, MAC_OS, TV_OS, WATCH_OS]
|
14
|
+
end
|
15
|
+
|
16
|
+
module ProcessStatus
|
17
|
+
PROCESSING = 'PROCESSING'
|
18
|
+
FAILED = 'FAILED'
|
19
|
+
INVALID = 'INVALID'
|
20
|
+
VALID = 'VALID'
|
21
|
+
end
|
22
|
+
|
23
|
+
module Model
|
24
|
+
def self.included(base)
|
25
|
+
Parser.types ||= []
|
26
|
+
Parser.types << base
|
27
|
+
base.extend(Parser)
|
28
|
+
end
|
29
|
+
|
30
|
+
attr_accessor :id
|
31
|
+
attr_reader :attributes
|
32
|
+
attr_reader :rate
|
33
|
+
|
34
|
+
def initialize(id, attributes, rate)
|
35
|
+
@id = id
|
36
|
+
@attributes = attributes
|
37
|
+
@rate = rate
|
38
|
+
|
39
|
+
update_attributes(attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_attributes(attributes)
|
43
|
+
(attributes || []).each do |key, value|
|
44
|
+
|
45
|
+
method = "#{underscore(key.to_s)}=".to_sym
|
46
|
+
self.send(method, value) if self.respond_to?(method)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def underscore(camel_cased_word)
|
53
|
+
return camel_cased_word.to_s unless /[A-Z-]/.match?(camel_cased_word)
|
54
|
+
|
55
|
+
word = ''.dup
|
56
|
+
camel_cased_word.each_char do |char|
|
57
|
+
if /[A-Z]/.match?(char)
|
58
|
+
word << '_' << char.downcase
|
59
|
+
else
|
60
|
+
word << char
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
word
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module Parser
|
69
|
+
class << self
|
70
|
+
attr_accessor :types
|
71
|
+
attr_accessor :types_cache
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.parse(response, rate)
|
75
|
+
body = response.body
|
76
|
+
data = body['data']
|
77
|
+
raise ConnectAPIError, 'No data' unless data
|
78
|
+
|
79
|
+
included = body['included'] || []
|
80
|
+
if data.kind_of?(Hash)
|
81
|
+
inflate_model(data, included, rate: rate)
|
82
|
+
elsif data.kind_of?(Array)
|
83
|
+
return data.map do |model_data|
|
84
|
+
inflate_model(model_data, included, rate: rate)
|
85
|
+
end
|
86
|
+
else
|
87
|
+
raise ConnectAPIError, "'data' is neither a hash nor an array"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.inflate_model(model_data, included, rate: {})
|
92
|
+
# Find class
|
93
|
+
type_class = find_class(model_data)
|
94
|
+
raise "No type class found for #{model_data['type']}" unless type_class
|
95
|
+
|
96
|
+
# Get id and attributes needed for inflating
|
97
|
+
id = model_data['id']
|
98
|
+
attributes = model_data['attributes']
|
99
|
+
|
100
|
+
# Instantiate object and inflate relationships
|
101
|
+
relationships = model_data['relationships'] || []
|
102
|
+
type_instance = type_class.new(id, attributes, rate)
|
103
|
+
|
104
|
+
inflate_model_relationships(type_instance, relationships, included)
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.find_class(model_data)
|
108
|
+
# Initialize cache
|
109
|
+
@types_cache ||= {}
|
110
|
+
|
111
|
+
# Find class in cache
|
112
|
+
type_string = model_data['type']
|
113
|
+
type_class = @types_cache[type_string]
|
114
|
+
return type_class if type_class
|
115
|
+
|
116
|
+
# Find class in array
|
117
|
+
type_class = @types.find do |type|
|
118
|
+
type.type == type_string
|
119
|
+
end
|
120
|
+
|
121
|
+
# Cache and return class
|
122
|
+
type_class
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.inflate_model_relationships(type_instance, relationships, included)
|
126
|
+
# Relationship attributes to set
|
127
|
+
attributes = {}
|
128
|
+
|
129
|
+
# 1. Iterate over relationships
|
130
|
+
# 2. Find id and type
|
131
|
+
# 3. Find matching id and type in included
|
132
|
+
# 4. Inflate matching data and set in attributes
|
133
|
+
relationships.each do |key, value|
|
134
|
+
# Validate data exists
|
135
|
+
value_data_or_datas = value['data']
|
136
|
+
next unless value_data_or_datas
|
137
|
+
|
138
|
+
# Map an included data object
|
139
|
+
map_data = lambda do |value_data|
|
140
|
+
id = value_data['id']
|
141
|
+
type = value_data['type']
|
142
|
+
|
143
|
+
relationship_data = included.find do |included_data|
|
144
|
+
id == included_data['id'] && type == included_data['type']
|
145
|
+
end
|
146
|
+
|
147
|
+
inflate_model(relationship_data, included) if relationship_data
|
148
|
+
end
|
149
|
+
|
150
|
+
# Map a hash or an array of data
|
151
|
+
if value_data_or_datas.kind_of?(Hash)
|
152
|
+
attributes[key] = map_data.call(value_data_or_datas)
|
153
|
+
elsif value_data_or_datas.kind_of?(Array)
|
154
|
+
attributes[key] = value_data_or_datas.map(&map_data)
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
type_instance.update_attributes(attributes)
|
159
|
+
type_instance
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
require_relative 'models/app'
|
165
|
+
require_relative 'models/app_store_version'
|
166
|
+
require_relative 'models/build'
|
167
|
+
require_relative 'models/pre_release_version'
|
168
|
+
require_relative 'models/app_store_version_submission'
|
169
|
+
require_relative 'models/device'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class App
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :name
|
11
|
+
attr_accessor :bundle_id
|
12
|
+
attr_accessor :sku
|
13
|
+
attr_accessor :primary_locale
|
14
|
+
attr_accessor :is_opted_in_to_distribute_ios_app_on_mac_app_store
|
15
|
+
attr_accessor :removed
|
16
|
+
attr_accessor :is_aag
|
17
|
+
attr_accessor :available_in_new_territories
|
18
|
+
attr_accessor :content_rights_declaration
|
19
|
+
|
20
|
+
# include
|
21
|
+
attr_accessor :app_store_versions
|
22
|
+
attr_accessor :builds
|
23
|
+
|
24
|
+
def self.type
|
25
|
+
'apps'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class AppStoreVersion
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :platform
|
11
|
+
attr_accessor :version_string
|
12
|
+
attr_accessor :app_store_state
|
13
|
+
attr_accessor :release_type
|
14
|
+
attr_accessor :earliest_release_date # 2020-06-17T12:00:00-07:00
|
15
|
+
attr_accessor :uses_idfa
|
16
|
+
attr_accessor :downloadable
|
17
|
+
attr_accessor :created_date
|
18
|
+
attr_accessor :version
|
19
|
+
attr_accessor :uploaded_date
|
20
|
+
attr_accessor :expiration_date
|
21
|
+
attr_accessor :expired
|
22
|
+
attr_accessor :store_icon
|
23
|
+
attr_accessor :watch_store_icon
|
24
|
+
attr_accessor :copyright
|
25
|
+
attr_accessor :min_os_version
|
26
|
+
|
27
|
+
# include
|
28
|
+
attr_accessor :app
|
29
|
+
attr_accessor :app_store_version_submission
|
30
|
+
attr_accessor :build
|
31
|
+
|
32
|
+
ESSENTIAL_INCLUDES = [
|
33
|
+
'app',
|
34
|
+
'appStoreVersionSubmission',
|
35
|
+
'build'
|
36
|
+
].join(',')
|
37
|
+
|
38
|
+
module AppStoreState
|
39
|
+
READY_FOR_SALE = 'READY_FOR_SALE'
|
40
|
+
PROCESSING_FOR_APP_STORE = 'PROCESSING_FOR_APP_STORE'
|
41
|
+
PENDING_DEVELOPER_RELEASE = 'PENDING_DEVELOPER_RELEASE'
|
42
|
+
IN_REVIEW = 'IN_REVIEW'
|
43
|
+
WAITING_FOR_REVIEW = 'WAITING_FOR_REVIEW'
|
44
|
+
DEVELOPER_REJECTED = 'DEVELOPER_REJECTED'
|
45
|
+
REJECTED = 'REJECTED'
|
46
|
+
PREPARE_FOR_SUBMISSION = 'PREPARE_FOR_SUBMISSION'
|
47
|
+
METADATA_REJECTED = 'METADATA_REJECTED'
|
48
|
+
INVALID_BINARY = 'INVALID_BINARY'
|
49
|
+
end
|
50
|
+
|
51
|
+
module ReleaseType
|
52
|
+
AFTER_APPROVAL = 'AFTER_APPROVAL'
|
53
|
+
MANUAL = 'MANUAL'
|
54
|
+
SCHEDULED = 'SCHEDULED'
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.type
|
58
|
+
'appStoreVersions'
|
59
|
+
end
|
60
|
+
|
61
|
+
def on_sale?
|
62
|
+
app_store_state == AppStoreState::READY_FOR_SALE ||
|
63
|
+
app_store_state == AppStoreState::PROCESSING_FOR_APP_STORE
|
64
|
+
end
|
65
|
+
|
66
|
+
def in_review?
|
67
|
+
app_store_state == AppStoreState::IN_REVIEW
|
68
|
+
end
|
69
|
+
|
70
|
+
def preparing?
|
71
|
+
app_store_state == AppStoreState::PREPARE_FOR_SUBMISSION ||
|
72
|
+
app_store_state == AppStoreState::DEVELOPER_REJECTED
|
73
|
+
end
|
74
|
+
|
75
|
+
def rejected?
|
76
|
+
app_store_state == AppStoreState::REJECTED ||
|
77
|
+
app_store_state == AppStoreState::METADATA_REJECTED ||
|
78
|
+
app_store_state == AppStoreState::INVALID_BINARY
|
79
|
+
end
|
80
|
+
|
81
|
+
def editable?
|
82
|
+
preparing? || rejected?
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class AppStoreVersionSubmission
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :can_reject
|
11
|
+
|
12
|
+
def self.type
|
13
|
+
'appStoreVersionSubmissions'
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class Build
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :version
|
11
|
+
attr_accessor :uploaded_date
|
12
|
+
attr_accessor :expiration_date
|
13
|
+
attr_accessor :expired
|
14
|
+
attr_accessor :min_os_version
|
15
|
+
attr_accessor :icon_asset_token
|
16
|
+
attr_accessor :processing_state
|
17
|
+
attr_accessor :uses_non_exempt_encryption
|
18
|
+
|
19
|
+
attr_accessor :app
|
20
|
+
attr_accessor :beta_app_review_submission
|
21
|
+
attr_accessor :beta_build_metrics
|
22
|
+
attr_accessor :build_beta_detail
|
23
|
+
attr_accessor :pre_release_version
|
24
|
+
|
25
|
+
ESSENTIAL_INCLUDES = 'app,preReleaseVersion'
|
26
|
+
|
27
|
+
def self.type
|
28
|
+
'builds'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class Device
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :device_class
|
11
|
+
attr_accessor :model
|
12
|
+
attr_accessor :name
|
13
|
+
attr_accessor :platform
|
14
|
+
attr_accessor :status
|
15
|
+
attr_accessor :udid
|
16
|
+
attr_accessor :added_date
|
17
|
+
|
18
|
+
# include
|
19
|
+
# attr_accessor :app
|
20
|
+
# attr_accessor :app_store_version_submission
|
21
|
+
# attr_accessor :build
|
22
|
+
|
23
|
+
module DeviceClass
|
24
|
+
APPLE_WATCH = "APPLE_WATCH"
|
25
|
+
IPAD = "IPAD"
|
26
|
+
IPHONE = "IPHONE"
|
27
|
+
IPOD = "IPOD"
|
28
|
+
APPLE_TV = "APPLE_TV"
|
29
|
+
MAC = "MAC"
|
30
|
+
end
|
31
|
+
|
32
|
+
module Status
|
33
|
+
ENABLED = "ENABLED"
|
34
|
+
DISABLED = "DISABLED"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.type
|
38
|
+
return "devices"
|
39
|
+
end
|
40
|
+
|
41
|
+
def enabled?
|
42
|
+
return status == Status::ENABLED
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
module TinyAppstoreConnect::Model
|
7
|
+
class PreReleaseVersion
|
8
|
+
include TinyAppstoreConnect::Model
|
9
|
+
|
10
|
+
attr_accessor :version
|
11
|
+
attr_accessor :platform
|
12
|
+
|
13
|
+
def self.type
|
14
|
+
return 'preReleaseVersions'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
require 'forwardable'
|
7
|
+
|
8
|
+
module TinyAppstoreConnect
|
9
|
+
class Response
|
10
|
+
include Enumerable
|
11
|
+
extend Forwardable
|
12
|
+
|
13
|
+
attr_reader :response, :connection
|
14
|
+
|
15
|
+
def initialize(response, connection)
|
16
|
+
@response = response
|
17
|
+
@connection = connection
|
18
|
+
end
|
19
|
+
|
20
|
+
def_delegators :@response, :status, :headers
|
21
|
+
|
22
|
+
def request_url
|
23
|
+
@response.env.url
|
24
|
+
end
|
25
|
+
|
26
|
+
def rate
|
27
|
+
return {} unless value = response.headers[:x_rate_limit]
|
28
|
+
|
29
|
+
value.split(';').inject({}) do |r, q|
|
30
|
+
k, v = q.split(':')
|
31
|
+
case k
|
32
|
+
when 'user-hour-lim'
|
33
|
+
r.merge!(limit: v)
|
34
|
+
when 'user-hour-rem'
|
35
|
+
r.merge!(remaining: v)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def next_link
|
41
|
+
return if response.nil?
|
42
|
+
|
43
|
+
links = response[:links] || {}
|
44
|
+
links[:next]
|
45
|
+
end
|
46
|
+
|
47
|
+
# def all_pages
|
48
|
+
# next_pages(count: 0)
|
49
|
+
# end
|
50
|
+
|
51
|
+
# def next_pages(count: 1)
|
52
|
+
# count = count.to_i
|
53
|
+
# count = 0 if count <= 0
|
54
|
+
|
55
|
+
# responses = [self]
|
56
|
+
# counter = 0
|
57
|
+
|
58
|
+
# resp = self
|
59
|
+
# loop do
|
60
|
+
# resp = resp.next_page
|
61
|
+
# break if resp.nil?
|
62
|
+
|
63
|
+
# responses << resp
|
64
|
+
# counter += 1
|
65
|
+
|
66
|
+
# break if counter >= count
|
67
|
+
# end
|
68
|
+
|
69
|
+
# responses
|
70
|
+
# end
|
71
|
+
|
72
|
+
# def next_url
|
73
|
+
# return if response.nil?
|
74
|
+
|
75
|
+
# links = response[:links] || {}
|
76
|
+
# links[:next]
|
77
|
+
# end
|
78
|
+
|
79
|
+
# def next_page
|
80
|
+
# return unless url = next_url
|
81
|
+
|
82
|
+
# Response.new(connection.get(url), connection)
|
83
|
+
# end
|
84
|
+
|
85
|
+
def to_model
|
86
|
+
to_models.first
|
87
|
+
end
|
88
|
+
|
89
|
+
def to_models
|
90
|
+
return [] if response.nil?
|
91
|
+
|
92
|
+
model_or_models = Parser.parse(response, rate)
|
93
|
+
[model_or_models].flatten
|
94
|
+
end
|
95
|
+
|
96
|
+
def each(&block)
|
97
|
+
to_models.each do |model|
|
98
|
+
yield(model)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Inspried from fastlane/spaceship
|
4
|
+
# License by fastlane team (https://github.com/fastlane/fastlane)
|
5
|
+
|
6
|
+
require 'jwt'
|
7
|
+
require 'openssl'
|
8
|
+
|
9
|
+
module TinyAppstoreConnect
|
10
|
+
class Token
|
11
|
+
# maximum expiration supported by AppStore (20 minutes)
|
12
|
+
# detail: https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests
|
13
|
+
DEFAULT_TOKEN_DURATION = 10 * 60
|
14
|
+
AUDIENCE = 'appstoreconnect-v1'
|
15
|
+
ALGORITHM = 'ES256'
|
16
|
+
|
17
|
+
attr_reader :issuer_id, :key_id, :private_key
|
18
|
+
|
19
|
+
def initialize(**kargs)
|
20
|
+
@duration = kargs[:duration] || DEFAULT_TOKEN_DURATION
|
21
|
+
@issuer_id = kargs[:issuer_id]
|
22
|
+
@key_id = kargs[:key_id]
|
23
|
+
@private_key = handle_private_key(kargs[:private_key])
|
24
|
+
end
|
25
|
+
|
26
|
+
def token
|
27
|
+
JWT.encode(payload, private_key, ALGORITHM, header_fields)
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def expired?
|
33
|
+
@expiration < Time.now
|
34
|
+
end
|
35
|
+
|
36
|
+
def payload
|
37
|
+
return @payload if @payload && !expired?
|
38
|
+
|
39
|
+
@expiration = Time.now + @duration
|
40
|
+
@payload = {
|
41
|
+
aud: AUDIENCE,
|
42
|
+
iss: issuer_id,
|
43
|
+
exp: @expiration.to_i
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def header_fields
|
48
|
+
@header_fields ||= { kid: key_id }
|
49
|
+
end
|
50
|
+
|
51
|
+
def handle_private_key(file)
|
52
|
+
io = File.file?(file) ? File.read(file) : file
|
53
|
+
OpenSSL::PKey.read(io)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'tiny_appstore_connect/version'
|
4
|
+
require_relative 'tiny_appstore_connect/token'
|
5
|
+
require_relative 'tiny_appstore_connect/model'
|
6
|
+
require_relative 'tiny_appstore_connect/response'
|
7
|
+
require_relative 'tiny_appstore_connect/client'
|
8
|
+
|
9
|
+
module TinyAppstoreConnect
|
10
|
+
class Error < StandardError; end
|
11
|
+
|
12
|
+
class ConnectAPIError < Error
|
13
|
+
class << self
|
14
|
+
def parse(response)
|
15
|
+
errors = response.body['errors']
|
16
|
+
case response.status
|
17
|
+
when 401
|
18
|
+
# Unauthorized
|
19
|
+
InvalidUserCredentialsError.from_errors(errors)
|
20
|
+
when 403
|
21
|
+
# The API key in use does not allow this request
|
22
|
+
ForbiddenError.from_errors(errors)
|
23
|
+
when 404
|
24
|
+
# Not found resource
|
25
|
+
NotFoundError.from_errors(errors)
|
26
|
+
when 409
|
27
|
+
# Invaild request entity
|
28
|
+
InvalidEntityError.from_errors(errors)
|
29
|
+
when 429
|
30
|
+
# 429 Rate Limit Exceeded
|
31
|
+
RateLimitExceededError.from_errors(errors)
|
32
|
+
else
|
33
|
+
ConnectAPIError.from_errors(errors)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def from_errors(errors)
|
38
|
+
message = ["Check errors(#{errors.size}) from response:"]
|
39
|
+
errors.each_with_index do |error, i|
|
40
|
+
message << "#{i + 1} - [#{error['status']}] #{error['title']}: #{error['detail']} in #{error['source']}"
|
41
|
+
end
|
42
|
+
|
43
|
+
new(message)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class RateLimitExceededError < ConnectAPIError; end
|
49
|
+
class InvalidEntityError < ConnectAPIError; end
|
50
|
+
class InvalidUserCredentialsError < ConnectAPIError; end
|
51
|
+
class ForbiddenError < ConnectAPIError; end
|
52
|
+
class NotFoundError < ConnectAPIError; end
|
53
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'lib/tiny_appstore_connect/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = 'tiny_appstore_connect'
|
7
|
+
spec.version = TinyAppstoreConnect::VERSION
|
8
|
+
spec.authors = ['icyleaf']
|
9
|
+
spec.email = ['icyleaf.cn@gmail.com']
|
10
|
+
|
11
|
+
spec.summary = 'Tiny AppStore Connect API'
|
12
|
+
spec.description = 'Tiny AppStore Connect API'
|
13
|
+
spec.homepage = 'https://github.com/icyleaf/tiny_appstore_connect'
|
14
|
+
spec.license = 'MIT'
|
15
|
+
spec.required_ruby_version = '>= 2.6.0'
|
16
|
+
|
17
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
18
|
+
spec.metadata['github_repo'] = 'ssh://github.com/icyleaf/tiny_appstore_connect'
|
19
|
+
|
20
|
+
|
21
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
23
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
24
|
+
end
|
25
|
+
end
|
26
|
+
spec.bindir = 'exe'
|
27
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
28
|
+
spec.require_paths = ['lib']
|
29
|
+
|
30
|
+
spec.add_dependency 'faraday', '>= 1.10.0', '< 3.0'
|
31
|
+
spec.add_dependency 'jwt', '>= 1.4', '<= 2.2.1'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tiny_appstore_connect
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- icyleaf
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-04-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.10.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.0'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.10.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: jwt
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.4'
|
40
|
+
- - "<="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.2.1
|
43
|
+
type: :runtime
|
44
|
+
prerelease: false
|
45
|
+
version_requirements: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '1.4'
|
50
|
+
- - "<="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 2.2.1
|
53
|
+
description: Tiny AppStore Connect API
|
54
|
+
email:
|
55
|
+
- icyleaf.cn@gmail.com
|
56
|
+
executables: []
|
57
|
+
extensions: []
|
58
|
+
extra_rdoc_files: []
|
59
|
+
files:
|
60
|
+
- ".rspec"
|
61
|
+
- ".rubocop.yml"
|
62
|
+
- CHANGELOG.md
|
63
|
+
- Gemfile
|
64
|
+
- LICENSE.txt
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- lib/tiny_appstore_connect.rb
|
68
|
+
- lib/tiny_appstore_connect/client.rb
|
69
|
+
- lib/tiny_appstore_connect/clients/app.rb
|
70
|
+
- lib/tiny_appstore_connect/clients/app_store_version.rb
|
71
|
+
- lib/tiny_appstore_connect/clients/build.rb
|
72
|
+
- lib/tiny_appstore_connect/clients/device.rb
|
73
|
+
- lib/tiny_appstore_connect/model.rb
|
74
|
+
- lib/tiny_appstore_connect/models/app.rb
|
75
|
+
- lib/tiny_appstore_connect/models/app_store_version.rb
|
76
|
+
- lib/tiny_appstore_connect/models/app_store_version_submission.rb
|
77
|
+
- lib/tiny_appstore_connect/models/build.rb
|
78
|
+
- lib/tiny_appstore_connect/models/device.rb
|
79
|
+
- lib/tiny_appstore_connect/models/pre_release_version.rb
|
80
|
+
- lib/tiny_appstore_connect/response.rb
|
81
|
+
- lib/tiny_appstore_connect/token.rb
|
82
|
+
- lib/tiny_appstore_connect/version.rb
|
83
|
+
- tiny_appstore_connect.gemspec
|
84
|
+
homepage: https://github.com/icyleaf/tiny_appstore_connect
|
85
|
+
licenses:
|
86
|
+
- MIT
|
87
|
+
metadata:
|
88
|
+
homepage_uri: https://github.com/icyleaf/tiny_appstore_connect
|
89
|
+
github_repo: ssh://github.com/icyleaf/tiny_appstore_connect
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 2.6.0
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubygems_version: 3.2.32
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Tiny AppStore Connect API
|
109
|
+
test_files: []
|