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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cff33ed704573f46fb622cd361572fa22147077a004fc0536de3b94468ec8fa4
4
- data.tar.gz: e33541f26974d0a4672fbadf064a26677a8f5fd4400b37cb231d39cf08559f2f
3
+ metadata.gz: 708041f71a3768ac678ff84edfd65e6934fafea29c26aef93bc3ddbc84ef7490
4
+ data.tar.gz: 365766a76b75d96603a698d49b5555cb840b92b1a61cf53eca7f7d74cea6b3d3
5
5
  SHA512:
6
- metadata.gz: 9d033f2fc9b88f3da5cbee8721735d691de2280fadde54e8e6ef9fb9534fbcd875b0a4914a2fa3f9533619c583cd41eb7681790d6d780f48badc68ee1a0d6b48
7
- data.tar.gz: c0e23d17dcc61170598a3263c2886f51202d06c0f44a6d68abe6189a9297cd7bdd10b5c04619d8a63d67c70ec5d59492880cb8bbae19a9126ecc4853ddca9d42
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
- raise UI.error("Please provide either Personal API Token (personalAPIToken) or Personal Access Key (personalAccessKey) to authenticate connections to Appcircle services")
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
- raise UI.error("Please provide only one authentication method: either Personal API Token (personalAPIToken) or Personal Access Key (personalAccessKey), not both")
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
- raise UI.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")
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
- raise UI.error("Please provide a summary for the application to be published. This summary will be displayed in the Appcircle Enterprise App Store")
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
- raise UI.error("Please provide release notes for the application to be published. These notes will be displayed in the Appcircle Enterprise App Store")
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
- raise UI.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")
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
- raise UI.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")
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
- begin
60
- user = AuthService.get_ac_token(pat: accessToken)
61
- UI.success("Login is successful.")
62
- @@apiToken = user.accessToken
63
- rescue => e
64
- UI.error("Login failed: #{e.message}")
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
- begin
71
- user = AuthService.get_ac_token_with_pak(personal_access_key: personalAccessKey)
72
- UI.success("Login is successful.")
73
- @@apiToken = user.accessToken
74
- rescue => e
75
- UI.error("Login failed: #{e.message}")
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("https://api.appcircle.io/task/v1/tasks/#{taskId}")
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.is_a?(Net::HTTPSuccess)
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
- raise UI.error("#{taskId} id upload request failed with status #{taskStatus[stateValue]}.")
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
- begin
127
- options = {
128
- auth_token: @@apiToken,
129
- ent_profile_id: entProfileId,
130
- ent_version_id: entVersionId,
131
- summary: summary,
132
- release_notes: releaseNote,
133
- publish_type: publishType
134
- }
135
- response = UploadService.publishVersion(options)
136
- rescue => e
137
- UI.error("App could not publish at Enterprise App Store. #{e&.response}")
138
- raise e
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
- env_name: "AC_PERSONAL_API_TOKEN",
171
- description: "Provide Personal API Token to authenticate Appcircle services (use either personalAPIToken or personalAccessKey)",
172
- optional: true,
173
- type: String),
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
- env_name: "AC_PERSONAL_ACCESS_KEY",
177
- description: "Provide Personal Access Key to authenticate Appcircle services (use either personalAPIToken or personalAccessKey)",
178
- optional: true,
179
- type: String),
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
- env_name: "AC_APP_PATH",
183
- 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",
184
- optional: false,
185
- type: String),
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
- env_name: "AC_SUMMARY",
189
- description: "Provide a summary for the application to be published. This summary will be displayed in the Appcircle Enterprise App Store",
190
- optional: false,
191
- type: String),
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
- env_name: "AC_RELEASE_NOTES",
195
- description: "Provide release notes for the application to be published. These notes will be displayed in the Appcircle Enterprise App Store",
196
- optional: false,
197
- type: String),
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
- env_name: "AC_PUBLISH_TYPE",
201
- 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",
202
- optional: false,
203
- type: String),
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 = 'https://auth.appcircle.io/auth/v2/token'
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.is_a?(Net::HTTPSuccess)
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 = 'https://auth.appcircle.io/auth/v1/token'
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.is_a?(Net::HTTPSuccess)
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.upload_artifact(token:, app:)
11
- url = "https://api.appcircle.io/store/v2/profiles/app-versions"
12
- headers = {
13
- Authorization: "Bearer #{token}",
14
- content_type: :multipart
15
- }
16
- payload = {
17
- File: File.new(app, 'rb')
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
- response = RestClient.post(url, payload, headers)
22
- JSON.parse(response.body) rescue response.body
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 = "#{BASE_URL}/store/v2/profiles/#{entProfileId}/app-versions"
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
- parsed_response = JSON.parse(response.body)
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
- begin
53
- if versionList.is_a?(Array) && !versionList.empty?
54
- return versionList[0]["id"]
55
- else
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
- 'Authorization': "Bearer #{options[:auth_token]}"
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 = "#{BASE_URL}/store/v2/profiles?Sort=desc"
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
- parsed_response = JSON.parse(response.body)
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
- sortedProfiles = profiles.sort_by { |profile| DateTime.parse(profile["lastBinaryReceivedDate"]) }.reverse
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
- return sortedProfiles[0]['id']
148
+ sortedProfiles[0] && sortedProfiles[0]['id']
109
149
  end
110
150
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AppcircleEnterpriseAppStore
3
- VERSION = "0.0.2"
3
+ VERSION = "0.1.0.beta.1"
4
4
  end
5
5
  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.2
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: 2025-11-25 00:00:00.000000000 Z
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: 'false'
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: '0'
59
+ version: 1.3.1
60
60
  requirements: []
61
- rubygems_version: 3.4.10
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