tiny_appstore_connect 0.1.3
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/.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: []
|