fastlane-plugin-appcircle_enterprise_app_store 0.0.2 → 0.1.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +19 -0
- data/lib/fastlane/plugin/appcircle_enterprise_app_store/actions/appcircle_enterprise_app_store_action.rb +86 -78
- data/lib/fastlane/plugin/appcircle_enterprise_app_store/helper/auth_service.rb +9 -12
- data/lib/fastlane/plugin/appcircle_enterprise_app_store/helper/upload_service.rb +77 -37
- data/lib/fastlane/plugin/appcircle_enterprise_app_store/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 708041f71a3768ac678ff84edfd65e6934fafea29c26aef93bc3ddbc84ef7490
|
|
4
|
+
data.tar.gz: 365766a76b75d96603a698d49b5555cb840b92b1a61cf53eca7f7d74cea6b3d3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a08f54eb84c62cf0ace4305f681744d70683740ca71571086d9f6ebf90764db680fae47cc43b781f45b5bb68b3e563cd535d1d25c9eaa05240fcfafb47ff041
|
|
7
|
+
data.tar.gz: 1d607adf62d805e9f696a1c36ee009de8e9208327ac8a519e7737ec8f34dc2302b8fb1afb1d6aa7902ba191ed5e6c2c8178878535532364af0db5c5f22d40bf1
|
data/README.md
CHANGED
|
@@ -82,6 +82,25 @@ After adding the plugin to your project, configure your Fastfile as follows:
|
|
|
82
82
|
- `Summary`: Used to provide a brief overview of the version of the app that is about to be published.
|
|
83
83
|
- `publishType`: Specifies the publishing status as either none, beta, or live, and must be assigned the values "0", "1", or "2" accordingly.
|
|
84
84
|
|
|
85
|
+
### Self-Hosted Appcircle
|
|
86
|
+
|
|
87
|
+
If you run a self-hosted Appcircle installation, point the action to your own endpoints with the optional `authEndpoint` and `apiEndpoint` parameters. Both default to the Appcircle cloud, so existing cloud users do not need to set them.
|
|
88
|
+
|
|
89
|
+
- `authEndpoint` (optional): Authentication endpoint URL. Defaults to `https://auth.appcircle.io`.
|
|
90
|
+
- `apiEndpoint` (optional): API endpoint URL. Defaults to `https://api.appcircle.io`.
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
appcircle_enterprise_app_store(
|
|
94
|
+
personalAPIToken: "$(AC_PERSONAL_API_TOKEN)",
|
|
95
|
+
authEndpoint: "https://auth.my-appcircle.example.com",
|
|
96
|
+
apiEndpoint: "https://api.my-appcircle.example.com",
|
|
97
|
+
appPath: "$(AC_APP_PATH)",
|
|
98
|
+
summary: "$(SUMMARY)",
|
|
99
|
+
releaseNotes: "$(RELEASE_NOTES)",
|
|
100
|
+
publishType: "$(PUBLISH_TYPE)"
|
|
101
|
+
)
|
|
102
|
+
```
|
|
103
|
+
|
|
85
104
|
**Ensure that this action is added after build steps have been completed.**
|
|
86
105
|
|
|
87
106
|
**If two workflows start simultaneously, the last workflow to reach the publish step will be the up-to-date version on the Enterprise App Store. If these workflows building the same package version, the first publish will be successful, while later deployments with the same version will fail.**
|
|
@@ -13,6 +13,8 @@ module Fastlane
|
|
|
13
13
|
module Actions
|
|
14
14
|
class AppcircleEnterpriseAppStoreAction < Action
|
|
15
15
|
@@apiToken = nil
|
|
16
|
+
@@authEndpoint = "https://auth.appcircle.io"
|
|
17
|
+
@@apiEndpoint = "https://api.appcircle.io"
|
|
16
18
|
|
|
17
19
|
def self.run(params)
|
|
18
20
|
personalAPIToken = params[:personalAPIToken]
|
|
@@ -21,6 +23,8 @@ module Fastlane
|
|
|
21
23
|
summary = params[:summary]
|
|
22
24
|
releaseNotes = params[:releaseNotes]
|
|
23
25
|
publishType = params[:publishType]
|
|
26
|
+
@@authEndpoint = params[:authEndpoint]
|
|
27
|
+
@@apiEndpoint = params[:apiEndpoint]
|
|
24
28
|
|
|
25
29
|
valid_extensions = ['.apk', '.ipa']
|
|
26
30
|
|
|
@@ -30,60 +34,53 @@ module Fastlane
|
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
if personalAPIToken.nil? && personalAccessKey.nil?
|
|
33
|
-
|
|
37
|
+
UI.user_error!("Please provide either Personal API Token (personalAPIToken) or Personal Access Key (personalAccessKey) to authenticate connections to Appcircle services")
|
|
34
38
|
elsif !personalAPIToken.nil? && !personalAccessKey.nil?
|
|
35
|
-
|
|
39
|
+
UI.user_error!("Please provide only one authentication method: either Personal API Token (personalAPIToken) or Personal Access Key (personalAccessKey), not both")
|
|
36
40
|
elsif appPath.nil?
|
|
37
|
-
|
|
41
|
+
UI.user_error!("Please specify the path to your application file. For iOS, this can be a .ipa or .xcarchive file path. For Android, specify the .apk or .appbundle file path")
|
|
38
42
|
elsif summary.nil?
|
|
39
|
-
|
|
43
|
+
UI.user_error!("Please provide a summary for the application to be published. This summary will be displayed in the Appcircle Enterprise App Store")
|
|
40
44
|
elsif releaseNotes.nil?
|
|
41
|
-
|
|
45
|
+
UI.user_error!("Please provide release notes for the application to be published. These notes will be displayed in the Appcircle Enterprise App Store")
|
|
42
46
|
elsif publishType.nil?
|
|
43
|
-
|
|
47
|
+
UI.user_error!("Please specify the publish type for the application. This can be 0: None, 1: Beta, 2: Live. Default is 0: None. For more information, provide the number of the publish type")
|
|
44
48
|
elsif publishType != "0" && publishType != "1" && publishType != "2"
|
|
45
|
-
|
|
49
|
+
UI.user_error!("Please provide a valid publish type. This can be 0: None, 1: Beta, 2: Live. Default is 0: None. For more information, provide the number of the publish type")
|
|
46
50
|
end
|
|
47
51
|
|
|
48
|
-
|
|
49
|
-
if !personalAPIToken.nil?
|
|
50
|
-
self.ac_login_with_pat(personalAPIToken)
|
|
51
|
-
else
|
|
52
|
+
if personalAPIToken.nil?
|
|
52
53
|
self.ac_login_with_pak(personalAccessKey)
|
|
54
|
+
else
|
|
55
|
+
self.ac_login_with_pat(personalAPIToken)
|
|
53
56
|
end
|
|
54
57
|
self.uploadToProfile(appPath, summary, releaseNotes, publishType)
|
|
55
58
|
end
|
|
56
59
|
|
|
57
|
-
|
|
58
60
|
def self.ac_login_with_pat(accessToken)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
raise e
|
|
66
|
-
end
|
|
61
|
+
user = AuthService.get_ac_token(pat: accessToken, auth_endpoint: @@authEndpoint)
|
|
62
|
+
UI.success("Login is successful.")
|
|
63
|
+
@@apiToken = user.accessToken
|
|
64
|
+
rescue StandardError => e
|
|
65
|
+
UI.error("Login failed: #{e.message}")
|
|
66
|
+
raise e
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def self.ac_login_with_pak(personalAccessKey)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
raise e
|
|
77
|
-
end
|
|
70
|
+
user = AuthService.get_ac_token_with_pak(personal_access_key: personalAccessKey, auth_endpoint: @@authEndpoint)
|
|
71
|
+
UI.success("Login is successful.")
|
|
72
|
+
@@apiToken = user.accessToken
|
|
73
|
+
rescue StandardError => e
|
|
74
|
+
UI.error("Login failed: #{e.message}")
|
|
75
|
+
raise e
|
|
78
76
|
end
|
|
79
77
|
|
|
80
|
-
|
|
81
78
|
def self.checkTaskStatus(taskId)
|
|
82
|
-
uri = URI.parse("
|
|
79
|
+
uri = URI.parse("#{@@apiEndpoint}/task/v1/tasks/#{taskId}")
|
|
83
80
|
timeout = 1
|
|
84
|
-
|
|
81
|
+
|
|
85
82
|
response = self.send_request(uri, @@apiToken)
|
|
86
|
-
if response.
|
|
83
|
+
if response.kind_of?(Net::HTTPSuccess)
|
|
87
84
|
stateValue = JSON.parse(response.body)["stateValue"]
|
|
88
85
|
if stateValue == 1
|
|
89
86
|
sleep(1)
|
|
@@ -96,24 +93,22 @@ module Fastlane
|
|
|
96
93
|
0 => "Unknown",
|
|
97
94
|
1 => "Begin",
|
|
98
95
|
2 => "Canceled",
|
|
99
|
-
3 => 'Completed'
|
|
96
|
+
3 => 'Completed'
|
|
100
97
|
}
|
|
101
|
-
|
|
98
|
+
UI.user_error!("#{taskId} id upload request failed with status #{taskStatus[stateValue]}.")
|
|
102
99
|
end
|
|
103
100
|
else
|
|
104
|
-
"Upload failed with response code #{response.code} and message '#{response.message}'"
|
|
105
|
-
raise
|
|
101
|
+
UI.user_error!("Upload failed with response code #{response.code} and message '#{response.message}'.")
|
|
106
102
|
end
|
|
107
103
|
end
|
|
108
104
|
|
|
109
|
-
|
|
110
105
|
def self.uploadToProfile(appPath, summary, releaseNotes, publishType)
|
|
111
|
-
response = UploadService.upload_artifact(token: @@apiToken, app: appPath)
|
|
106
|
+
response = UploadService.upload_artifact(token: @@apiToken, app: appPath, api_endpoint: @@apiEndpoint)
|
|
112
107
|
result = self.checkTaskStatus(response["taskId"])
|
|
113
108
|
|
|
114
109
|
if result
|
|
115
|
-
profileId = UploadService.getProfileId(authToken: @@apiToken)
|
|
116
|
-
appVersions = UploadService.getAppVersions(auth_token: @@apiToken, entProfileId: profileId)
|
|
110
|
+
profileId = UploadService.getProfileId(authToken: @@apiToken, api_endpoint: @@apiEndpoint)
|
|
111
|
+
appVersions = UploadService.getAppVersions(auth_token: @@apiToken, entProfileId: profileId, api_endpoint: @@apiEndpoint)
|
|
117
112
|
appVersionId = UploadService.getVersionId(versionList: appVersions)
|
|
118
113
|
if publishType != "0"
|
|
119
114
|
self.publishToStore(profileId, appVersionId, summary, releaseNotes, publishType)
|
|
@@ -123,20 +118,19 @@ module Fastlane
|
|
|
123
118
|
end
|
|
124
119
|
|
|
125
120
|
def self.publishToStore(entProfileId, entVersionId, summary, releaseNote, publishType)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
end
|
|
121
|
+
options = {
|
|
122
|
+
auth_token: @@apiToken,
|
|
123
|
+
ent_profile_id: entProfileId,
|
|
124
|
+
ent_version_id: entVersionId,
|
|
125
|
+
summary: summary,
|
|
126
|
+
release_notes: releaseNote,
|
|
127
|
+
publish_type: publishType,
|
|
128
|
+
api_endpoint: @@apiEndpoint
|
|
129
|
+
}
|
|
130
|
+
response = UploadService.publishVersion(options)
|
|
131
|
+
rescue StandardError => e
|
|
132
|
+
UI.error("App could not publish at Enterprise App Store. #{e&.response}")
|
|
133
|
+
raise e
|
|
140
134
|
end
|
|
141
135
|
|
|
142
136
|
def self.send_request(uri, access_token)
|
|
@@ -167,40 +161,54 @@ module Fastlane
|
|
|
167
161
|
def self.available_options
|
|
168
162
|
[
|
|
169
163
|
FastlaneCore::ConfigItem.new(key: :personalAPIToken,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
164
|
+
env_name: "AC_PERSONAL_API_TOKEN",
|
|
165
|
+
description: "Provide Personal API Token to authenticate Appcircle services (use either personalAPIToken or personalAccessKey)",
|
|
166
|
+
optional: true,
|
|
167
|
+
type: String),
|
|
174
168
|
|
|
175
169
|
FastlaneCore::ConfigItem.new(key: :personalAccessKey,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
env_name: "AC_PERSONAL_ACCESS_KEY",
|
|
171
|
+
description: "Provide Personal Access Key to authenticate Appcircle services (use either personalAPIToken or personalAccessKey)",
|
|
172
|
+
optional: true,
|
|
173
|
+
type: String),
|
|
174
|
+
|
|
175
|
+
FastlaneCore::ConfigItem.new(key: :authEndpoint,
|
|
176
|
+
env_name: "AC_AUTH_ENDPOINT",
|
|
177
|
+
description: "Optional: Authentication endpoint URL for self-hosted Appcircle installations. Defaults to the Appcircle cloud",
|
|
178
|
+
optional: true,
|
|
179
|
+
default_value: "https://auth.appcircle.io",
|
|
180
|
+
type: String),
|
|
181
|
+
|
|
182
|
+
FastlaneCore::ConfigItem.new(key: :apiEndpoint,
|
|
183
|
+
env_name: "AC_API_ENDPOINT",
|
|
184
|
+
description: "Optional: API endpoint URL for self-hosted Appcircle installations. Defaults to the Appcircle cloud",
|
|
185
|
+
optional: true,
|
|
186
|
+
default_value: "https://api.appcircle.io",
|
|
187
|
+
type: String),
|
|
180
188
|
|
|
181
189
|
FastlaneCore::ConfigItem.new(key: :appPath,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
190
|
+
env_name: "AC_APP_PATH",
|
|
191
|
+
description: "Specify the path to your application file. For iOS, this can be a .ipa or .xcarchive file path. For Android, specify the .apk or .appbundle file path",
|
|
192
|
+
optional: false,
|
|
193
|
+
type: String),
|
|
186
194
|
|
|
187
195
|
FastlaneCore::ConfigItem.new(key: :summary,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
196
|
+
env_name: "AC_SUMMARY",
|
|
197
|
+
description: "Provide a summary for the application to be published. This summary will be displayed in the Appcircle Enterprise App Store",
|
|
198
|
+
optional: false,
|
|
199
|
+
type: String),
|
|
192
200
|
|
|
193
201
|
FastlaneCore::ConfigItem.new(key: :releaseNotes,
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
202
|
+
env_name: "AC_RELEASE_NOTES",
|
|
203
|
+
description: "Provide release notes for the application to be published. These notes will be displayed in the Appcircle Enterprise App Store",
|
|
204
|
+
optional: false,
|
|
205
|
+
type: String),
|
|
198
206
|
|
|
199
207
|
FastlaneCore::ConfigItem.new(key: :publishType,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
208
|
+
env_name: "AC_PUBLISH_TYPE",
|
|
209
|
+
description: "Specify the publish type for the application. This can be 0: None, 1: Beta, 2: Live. Default is 0: None. For more information, provide the number of the publish type",
|
|
210
|
+
optional: false,
|
|
211
|
+
type: String)
|
|
204
212
|
]
|
|
205
213
|
end
|
|
206
214
|
|
|
@@ -3,7 +3,6 @@ require 'uri'
|
|
|
3
3
|
require 'cgi'
|
|
4
4
|
require 'json'
|
|
5
5
|
|
|
6
|
-
|
|
7
6
|
class UserResponse
|
|
8
7
|
attr_accessor :accessToken
|
|
9
8
|
|
|
@@ -13,15 +12,15 @@ class UserResponse
|
|
|
13
12
|
end
|
|
14
13
|
|
|
15
14
|
module AuthService
|
|
16
|
-
def self.get_ac_token(pat:)
|
|
17
|
-
endpoint_url =
|
|
15
|
+
def self.get_ac_token(pat:, auth_endpoint: 'https://auth.appcircle.io')
|
|
16
|
+
endpoint_url = "#{auth_endpoint}/auth/v2/token"
|
|
18
17
|
uri = URI(endpoint_url)
|
|
19
18
|
|
|
20
19
|
# Create HTTP request
|
|
21
20
|
request = Net::HTTP::Post.new(uri)
|
|
22
21
|
request.content_type = 'application/x-www-form-urlencoded'
|
|
23
22
|
request['Accept'] = 'application/json'
|
|
24
|
-
|
|
23
|
+
|
|
25
24
|
# Encode parameters
|
|
26
25
|
params = { pat: pat }
|
|
27
26
|
request.body = URI.encode_www_form(params)
|
|
@@ -31,10 +30,8 @@ module AuthService
|
|
|
31
30
|
http.request(request)
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
33
|
# Check response
|
|
37
|
-
if response.
|
|
34
|
+
if response.kind_of?(Net::HTTPSuccess)
|
|
38
35
|
response_data = JSON.parse(response.body)
|
|
39
36
|
|
|
40
37
|
user = UserResponse.new(
|
|
@@ -47,15 +44,15 @@ module AuthService
|
|
|
47
44
|
end
|
|
48
45
|
end
|
|
49
46
|
|
|
50
|
-
def self.get_ac_token_with_pak(personal_access_key:)
|
|
51
|
-
endpoint_url =
|
|
47
|
+
def self.get_ac_token_with_pak(personal_access_key:, auth_endpoint: 'https://auth.appcircle.io')
|
|
48
|
+
endpoint_url = "#{auth_endpoint}/auth/v1/token"
|
|
52
49
|
uri = URI(endpoint_url)
|
|
53
50
|
|
|
54
51
|
# Create HTTP request
|
|
55
52
|
request = Net::HTTP::Post.new(uri)
|
|
56
53
|
request.content_type = 'application/x-www-form-urlencoded'
|
|
57
54
|
request['Accept'] = 'application/json'
|
|
58
|
-
|
|
55
|
+
|
|
59
56
|
# Encode parameters
|
|
60
57
|
params = { 'personal-access-key' => personal_access_key }
|
|
61
58
|
request.body = URI.encode_www_form(params)
|
|
@@ -66,7 +63,7 @@ module AuthService
|
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
# Check response
|
|
69
|
-
if response.
|
|
66
|
+
if response.kind_of?(Net::HTTPSuccess)
|
|
70
67
|
response_data = JSON.parse(response.body)
|
|
71
68
|
|
|
72
69
|
user = UserResponse.new(
|
|
@@ -78,4 +75,4 @@ module AuthService
|
|
|
78
75
|
raise "HTTP Request failed (#{response.code} #{response.message})"
|
|
79
76
|
end
|
|
80
77
|
end
|
|
81
|
-
end
|
|
78
|
+
end
|
|
@@ -5,21 +5,62 @@ require 'rest-client'
|
|
|
5
5
|
|
|
6
6
|
BASE_URL = "https://api.appcircle.io"
|
|
7
7
|
|
|
8
|
-
|
|
9
8
|
module UploadService
|
|
10
|
-
def self.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
def self.put_with_retry(url, body, headers, max_retries: 5)
|
|
10
|
+
attempt = 0
|
|
11
|
+
delay = 1.0
|
|
12
|
+
|
|
13
|
+
begin
|
|
14
|
+
RestClient.put(url, body, headers)
|
|
15
|
+
rescue => e
|
|
16
|
+
status = e.respond_to?(:http_code) ? e.http_code : nil
|
|
17
|
+
retryable = status == 503 ||
|
|
18
|
+
e.is_a?(RestClient::ServerBrokeConnection) ||
|
|
19
|
+
e.is_a?(Errno::ECONNRESET) ||
|
|
20
|
+
(defined?(RestClient::Exceptions::OpenTimeout) && e.is_a?(RestClient::Exceptions::OpenTimeout)) ||
|
|
21
|
+
(defined?(RestClient::Exceptions::ReadTimeout) && e.is_a?(RestClient::Exceptions::ReadTimeout))
|
|
22
|
+
|
|
23
|
+
raise e if !retryable || attempt >= max_retries
|
|
24
|
+
|
|
25
|
+
attempt += 1
|
|
26
|
+
sleep(delay + rand(0.3)) # backoff + jitter (0-300ms)
|
|
27
|
+
delay *= 2
|
|
28
|
+
retry
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
19
32
|
|
|
33
|
+
def self.upload_artifact(token:, app:, api_endpoint: BASE_URL)
|
|
34
|
+
file_path = app
|
|
35
|
+
file_name = File.basename(file_path)
|
|
36
|
+
file_size = File.size(file_path)
|
|
37
|
+
auth_header = { Authorization: "Bearer #{token}", accept: 'application/json' }
|
|
38
|
+
|
|
20
39
|
begin
|
|
21
|
-
|
|
22
|
-
|
|
40
|
+
info_uri = URI("#{api_endpoint}/store/v1/profiles/app-versions")
|
|
41
|
+
info_uri.query = URI.encode_www_form({ action: 'uploadInformation', fileName: file_name, fileSize: file_size })
|
|
42
|
+
upload_info = JSON.parse(RestClient.get(info_uri.to_s, auth_header).body)
|
|
43
|
+
file_id = upload_info['fileId']
|
|
44
|
+
upload_url = upload_info['uploadUrl']
|
|
45
|
+
configuration = upload_info['configuration']
|
|
46
|
+
http_method = (configuration && configuration['httpMethod']) ? configuration['httpMethod'].to_s.upcase : 'PUT'
|
|
47
|
+
|
|
48
|
+
if http_method == 'POST'
|
|
49
|
+
sign_parameters = configuration['signParameters'] || {}
|
|
50
|
+
payload = {}
|
|
51
|
+
sign_parameters.each { |key, value| payload[key] = value }
|
|
52
|
+
payload['file'] = File.new(file_path, 'rb') # the 'file' field MUST be last
|
|
53
|
+
RestClient.post(upload_url, payload)
|
|
54
|
+
else
|
|
55
|
+
put_with_retry(upload_url, File.binread(file_path), { content_type: 'application/octet-stream' })
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
commit_uri = URI("#{api_endpoint}/store/v1/profiles/app-versions")
|
|
59
|
+
commit_uri.query = URI.encode_www_form({ action: 'commitFileUpload', createNewProfile: true })
|
|
60
|
+
|
|
61
|
+
commit_payload = { fileId: file_id, fileName: file_name }.to_json
|
|
62
|
+
commit_headers = { Authorization: "Bearer #{token}", content_type: :json, accept: 'application/json' }
|
|
63
|
+
JSON.parse(RestClient.post(commit_uri.to_s, commit_payload, commit_headers).body)
|
|
23
64
|
rescue RestClient::ExceptionWithResponse => e
|
|
24
65
|
raise e
|
|
25
66
|
rescue StandardError => e
|
|
@@ -27,8 +68,8 @@ module UploadService
|
|
|
27
68
|
end
|
|
28
69
|
end
|
|
29
70
|
|
|
30
|
-
def self.getAppVersions(auth_token:, entProfileId:)
|
|
31
|
-
url = "#{
|
|
71
|
+
def self.getAppVersions(auth_token:, entProfileId:, api_endpoint: BASE_URL)
|
|
72
|
+
url = "#{api_endpoint}/store/v2/profiles/#{entProfileId}/app-versions"
|
|
32
73
|
|
|
33
74
|
# Set up the headers with authentication
|
|
34
75
|
headers = {
|
|
@@ -38,9 +79,7 @@ module UploadService
|
|
|
38
79
|
|
|
39
80
|
begin
|
|
40
81
|
response = RestClient.get(url, headers)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
parsed_response
|
|
82
|
+
JSON.parse(response.body)
|
|
44
83
|
rescue RestClient::ExceptionWithResponse => e
|
|
45
84
|
raise e
|
|
46
85
|
rescue StandardError => e
|
|
@@ -49,21 +88,19 @@ module UploadService
|
|
|
49
88
|
end
|
|
50
89
|
|
|
51
90
|
def self.getVersionId(versionList:)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return nil
|
|
57
|
-
end
|
|
58
|
-
rescue => e
|
|
59
|
-
puts "An error occurred while getting app versions: #{e.message}"
|
|
60
|
-
raise e
|
|
91
|
+
if versionList.kind_of?(Array) && !versionList.empty?
|
|
92
|
+
return versionList[0]["id"]
|
|
93
|
+
else
|
|
94
|
+
return nil
|
|
61
95
|
end
|
|
96
|
+
rescue StandardError => e
|
|
97
|
+
puts("An error occurred while getting app versions: #{e.message}")
|
|
98
|
+
raise e
|
|
62
99
|
end
|
|
63
100
|
|
|
64
101
|
def self.publishVersion(options)
|
|
65
102
|
endpoint = "store/v2/profiles/#{options[:ent_profile_id]}/app-versions/#{options[:ent_version_id]}?action=publish"
|
|
66
|
-
url = "#{BASE_URL}/#{endpoint}"
|
|
103
|
+
url = "#{options[:api_endpoint] || BASE_URL}/#{endpoint}"
|
|
67
104
|
|
|
68
105
|
payload = {
|
|
69
106
|
summary: options[:summary],
|
|
@@ -73,7 +110,7 @@ module UploadService
|
|
|
73
110
|
|
|
74
111
|
headers = {
|
|
75
112
|
'Content-Type': 'application/json',
|
|
76
|
-
|
|
113
|
+
Authorization: "Bearer #{options[:auth_token]}"
|
|
77
114
|
}
|
|
78
115
|
|
|
79
116
|
response = RestClient.patch(url, payload.to_json, headers)
|
|
@@ -82,8 +119,8 @@ module UploadService
|
|
|
82
119
|
raise e
|
|
83
120
|
end
|
|
84
121
|
|
|
85
|
-
def self.getEntProfiles(authToken:)
|
|
86
|
-
url = "#{
|
|
122
|
+
def self.getEntProfiles(authToken:, api_endpoint: BASE_URL)
|
|
123
|
+
url = "#{api_endpoint}/store/v2/profiles"
|
|
87
124
|
headers = {
|
|
88
125
|
Authorization: "Bearer #{authToken}",
|
|
89
126
|
accept: 'application/json'
|
|
@@ -91,9 +128,7 @@ module UploadService
|
|
|
91
128
|
|
|
92
129
|
begin
|
|
93
130
|
response = RestClient.get(url, headers)
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
parsed_response
|
|
131
|
+
JSON.parse(response.body)
|
|
97
132
|
rescue RestClient::ExceptionWithResponse => e
|
|
98
133
|
raise e
|
|
99
134
|
rescue StandardError => e
|
|
@@ -101,10 +136,15 @@ module UploadService
|
|
|
101
136
|
end
|
|
102
137
|
end
|
|
103
138
|
|
|
104
|
-
def self.getProfileId(authToken:)
|
|
105
|
-
profiles = self.getEntProfiles(authToken: authToken)
|
|
106
|
-
|
|
139
|
+
def self.getProfileId(authToken:, api_endpoint: BASE_URL)
|
|
140
|
+
profiles = self.getEntProfiles(authToken: authToken, api_endpoint: api_endpoint)
|
|
141
|
+
return nil unless profiles.is_a?(Array) && !profiles.empty?
|
|
142
|
+
|
|
143
|
+
sortedProfiles = profiles.sort_by do |profile|
|
|
144
|
+
date = profile["lastBinaryReceivedDate"]
|
|
145
|
+
date ? DateTime.parse(date) : DateTime.new(0)
|
|
146
|
+
end.reverse
|
|
107
147
|
|
|
108
|
-
|
|
148
|
+
sortedProfiles[0] && sortedProfiles[0]['id']
|
|
109
149
|
end
|
|
110
150
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fastlane-plugin-appcircle_enterprise_app_store
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.1.0.beta.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- appcircleio
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-06-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rest-client
|
|
@@ -42,7 +42,7 @@ homepage: https://github.com/appcircleio/fastlane-plugin-appcircle_enterprise_ap
|
|
|
42
42
|
licenses:
|
|
43
43
|
- MIT
|
|
44
44
|
metadata:
|
|
45
|
-
rubygems_mfa_required: '
|
|
45
|
+
rubygems_mfa_required: 'true'
|
|
46
46
|
post_install_message:
|
|
47
47
|
rdoc_options: []
|
|
48
48
|
require_paths:
|
|
@@ -54,11 +54,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
54
54
|
version: '2.6'
|
|
55
55
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
56
56
|
requirements:
|
|
57
|
-
- - "
|
|
57
|
+
- - ">"
|
|
58
58
|
- !ruby/object:Gem::Version
|
|
59
|
-
version:
|
|
59
|
+
version: 1.3.1
|
|
60
60
|
requirements: []
|
|
61
|
-
rubygems_version: 3.
|
|
61
|
+
rubygems_version: 3.3.27
|
|
62
62
|
signing_key:
|
|
63
63
|
specification_version: 4
|
|
64
64
|
summary: Efficiently publish your apps to Appcircle Enterprise Store
|