fastlane-plugin-appcircle_testing_distribution 0.4.0 → 0.4.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: e01adda887a2e03213c82a2f1dd4c73e068599c6f2a72107663bc40b0246259d
4
- data.tar.gz: 551d74f7d9d2ba2f9683fbc20c69c7e06c8897c7a711d98a6d636ce50fab7d82
3
+ metadata.gz: ceb1940fa8c0d58e985bded6be73f61f64afb0599e852c52431811159af01212
4
+ data.tar.gz: 130705717aba452336764a57fe29a5c14c29b648e8d528e1ce3edae406e66359
5
5
  SHA512:
6
- metadata.gz: d77f4bf600f1281ac845c37c5a37cbe76c2ffffa3630f6d27d12e793912c80c01197ec746c2ef8d15f2c4c7a75e2798faeae10bad0485bb3e1ee5d65fbd5c472
7
- data.tar.gz: fa09b51a8528c0693bf8a4371b29fd3ba1a87e42185069599744f28d0d06eab8430f8f38af5198fce624b9483ca0e20b6ad51df9f8cdc1565dcec150c7015ff1
6
+ metadata.gz: c1537e66400a4f57df3553ce8ef8fcadc89bee630557aa2eed668e7862e32327f391e0e6c280a7b01d7b9568ce3e3677608d771f089a4eb9aeb79f9ee0792405
7
+ data.tar.gz: f39fbbe473f79aceeac915a63fdbe9a638eb56a91172b8c7403d34367d95883a31e32f78cafb4fdb9ef3526108b534c4efb58a449f740ea906264aed08e4fb4b
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2024 Appcircle, Inc. <info@appcircle.io>
3
+ Copyright (c) 2024 Guven Karanfil <guven.karanfil@smartface.io>
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Appcircle Testing Distribution
1
+ ## Appcircle Testing Distribution
2
2
 
3
3
  [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-appcircle_testing_distribution)
4
4
 
@@ -38,11 +38,24 @@ Testing distribution is the process of distributing test builds to designated te
38
38
 
39
39
  Overall, using testing distribution in mobile DevOps significantly enhances the efficiency, security, and effectiveness of the software development process, leading to better products and faster delivery times.
40
40
 
41
- <!-- ## Testing Distribution
41
+ ## System Requirements
42
42
 
43
- In order to share your builds with testers, you can create testing distribution profiles and assign testing groups to the profiles.
43
+ **Compatible Agents:**
44
44
 
45
- ![Testing Distribution Profile](<https://cdn.appcircle.io/docs/assets/image%20(152).png>)
45
+ - macOS 14.2, 14.5
46
+
47
+ **Supported Version:**
48
+
49
+ - Fastlane 2.222.0
50
+ - Ruby 3.2.2
51
+
52
+ Note: Currently, plugins are only compatible to use with **Appcircle Cloud**. **Self-hosted** support will be available in future releases.
53
+
54
+ ## Testing Distribution
55
+
56
+ In order to share your builds with testers, you can create distribution profiles and assign testing groups to the distribution profiles.
57
+
58
+ ![Distribution Profile](<https://cdn.appcircle.io/docs/assets/image%20(152).png>)
46
59
 
47
60
  ## Generating/Managing the Personal API Tokens
48
61
 
@@ -52,11 +65,11 @@ To generate a Personal API Token, follow these steps:
52
65
  2. You'll find the Personal API Token section in the top right corner.
53
66
  3. Press the "Generate Token" button to generate your first token.
54
67
 
55
- ![Token Generation](<https://cdn.appcircle.io/docs/assets/image%20(164).png>) -->
68
+ ![Token Generation](<https://cdn.appcircle.io/docs/assets/image%20(164).png>)
56
69
 
57
70
  ## Getting Started with the Extension: Usage Guide
58
71
 
59
- To share your builds with testers, you can create testing distribution profiles and assign testing groups to these profiles.
72
+ To share your builds with testers, you can create distribution profiles and assign testing groups to these profiles.
60
73
 
61
74
  This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-appcircle_testing_distribution`, add it to your project by running:
62
75
 
@@ -64,48 +77,31 @@ This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To
64
77
  fastlane add_plugin appcircle_testing_distribution
65
78
  ```
66
79
 
67
- ```ruby
80
+ ```yml
68
81
  appcircle_testing_distribution(
69
- personalAPIToken: ENV["AC_PERSONAL_API_TOKEN"],
70
- subOrganizationName: ENV["AC_SUB_ORGANIZATION_NAME"],
71
- profileName: ENV["AC_PROFILE_NAME"],
72
- createProfileIfNotExists: ENV["AC_CREATE_PROFILE_IF_NOT_EXISTS"],
73
- profileCreationSettings: {
74
- authType: ENV["AC_PROFILE_AUTH_TYPE"],
75
- username: ENV["AC_PROFILE_USERNAME"],
76
- password: ENV["AC_PROFILE_PASSWORD"],
77
- testingGroupNames: ENV["AC_PROFILE_TESTING_GROUP_NAMES"]
78
- },
79
- appPath: ENV["AC_APP_PATH"],
80
- message: ENV["AC_MESSAGE"]
82
+ personalAPIToken: "$(AC_PERSONAL_API_TOKEN)",
83
+ profileName: "$(AC_PROFILE_NAME)",
84
+ createProfileIfNotExists: Boolean,
85
+ appPath: "$(AC_APP_PATH)",
86
+ message: "$(AC_MESSAGE)",
81
87
  )
82
88
  ```
83
89
 
84
- - `personalAPIToken`: The Appcircle Personal API token used to authenticate and authorize access to Appcircle services within this plugin.
85
- - `subOrganizationName` (optional): Required when the Root Organization's `personalAPIToken` is used, and you want to create the profile under a sub-organization. In this case, provide the name of the sub-organization in this field. If you directly used the sub-organization's `personalAPIToken`, this parameter is not needed.
90
+ - `personalAPIToken`: The Appcircle Personal API token is utilized to authenticate and secure access to Appcircle services, ensuring that only authorized users can perform actions within the platform.
86
91
  - `profileName`: Specifies the profile that will be used for uploading the app.
87
- - `createProfileIfNotExists` (optional): Ensures that a testing distribution profile is automatically created if it does not already exist; if the profile name already exists, the app will be uploaded to that existing profile instead.
88
- - `profileCreationSettings` (optional): If `createProfileIfNotExists` is `true` and a new profile being created, the profile will be configured with these settings.
89
- - `authType`: Authentication type of the profile. `none`: None, `static`: Static Username and Password, `ldap`: LDAP Login, `sso`: SSO Login.
90
- - `username`: The username for the profile if authentication type set to `static` (Static Username and Password).
91
- - `password`: The password for the profile if authentication type set to `static` (Static Username and Password).
92
- - `testingGroupNames`: Uploaded versions will be automatically shared with these testing groups. Example format: `group1, group2, group3`.
93
- - `appPath`: Indicates the file path to the application package that will be uploaded to Appcircle Testing Distribution Profile.
92
+ - `createProfileIfNotExists`: Ensures that a user profile is automatically created if it does not already exist; if the profile name already exists, the app will be uploaded to that existing profile instead.
93
+ - `appPath`: Indicates the file path to the application that will be uploaded to Appcircle Testing Distribution Profile.
94
94
  - `message`: Your message to testers, ensuring they receive important updates and information regarding the application.
95
95
 
96
- ## Further Details
96
+ ### Leveraging Environment Variables
97
97
 
98
- For more information please refer to the documentation.
98
+ Utilize environment variables seamlessly by substituting the parameters with `$(VARIABLE_NAME)` in your task inputs. The extension automatically retrieves values from the specified environment variables within your pipeline.
99
99
 
100
- - [Setting Up Appcircle Testing Distribution Plugin](https://docs.appcircle.io/marketplace/fastlane/testing-distribution)
101
- - [Discover Action](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#discover-action)
102
- - [System Requirements](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#system-requirements)
103
- - [User Permission Requirements](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#user-permission-requirements)
104
- - [How to Add the Appcircle Distribute Action to Your Pipeline](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#how-to-add-the-appcircle-distribute-action-to-your-pipeline)
105
- - [CLI Usage](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#cli-usage)
106
- - [Distributing to Sub-Organizations](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#distributing-to-sub-organizations)
107
- - [Leveraging Environment Variables](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#leveraging-environment-variables)
108
- - [References](https://docs.appcircle.io/marketplace/fastlane/testing-distribution#references)
100
+ **Ensure that this action is added after build steps have been completed.**
101
+
102
+ **If multiple workflows start simultaneously, the order in which versions are shared in the Testing Distribution is determined by the execution order of the publish step. The version that completes its build and triggers the publish plugin first will be shared first, followed by the others in sequence.**
103
+
104
+ Efficiently distribute test binaries or beta versions using Appcircle, featuring seamless IPA and APK distribution capabilities. Streamline your testing process with our versatile tool designed to optimize your distribution workflow. If you need support or more information, please [contact us](https://appcircle.io/contact?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
109
105
 
110
106
  ## Issues and Feedback
111
107
 
@@ -114,3 +110,9 @@ For any other issues and feedback about this plugin, please submit it to this re
114
110
  ## Troubleshooting
115
111
 
116
112
  If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
113
+
114
+ ## Reference
115
+
116
+ - For details on generating an Appcircle Personal API Token, visit [Generating/Managing Personal API Tokens](https://docs.appcircle.io/appcircle-api/api-authentication#generatingmanaging-the-personal-api-tokens?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
117
+
118
+ - To create or learn more about Appcircle testing and distribution profiles, please refer to [Creating or Selecting a Distribution Profile](https://docs.appcircle.io/distribute/create-or-select-a-distribution-profile?utm_source=fastlane&utm_medium=plugin&utm_campaign=testing_distribution)
data/images/PAT.png ADDED
Binary file
Binary file
@@ -10,127 +10,74 @@ require_relative '../helper/TDUploadService'
10
10
  module Fastlane
11
11
  module Actions
12
12
  class AppcircleTestingDistributionAction < Action
13
- VALID_EXTENSIONS = ['.apk', '.aab', '.ipa', '.zip']
14
- AUTH_TYPE_MAPPING = {
15
- 'none' => 1, # None
16
- 'static' => 3, # Static Username and Password
17
- 'ldap' => 4, # LDAP Login
18
- 'sso' => 5 # SSO Login
19
- }
20
-
21
13
  def self.run(params)
22
14
  personalAPIToken = params[:personalAPIToken]
23
- subOrganizationName = params[:subOrganizationName]
15
+ personalAccessKey = params[:personalAccessKey]
24
16
  profileName = params[:profileName]
25
- createProfileIfNotExists = params[:createProfileIfNotExists] || false
26
- #
27
- profileCreationSettings = params[:profileCreationSettings]
28
- profileAuthType = profileCreationSettings&.dig(:authType)
29
- profileUsername = profileCreationSettings&.dig(:username)
30
- profilePassword = profileCreationSettings&.dig(:password)
31
- profileTestingGroupNames= profileCreationSettings&.dig(:testingGroupNames)
32
- #
33
17
  appPath = params[:appPath]
34
18
  message = params[:message]
35
-
36
- profileAuthType = AUTH_TYPE_MAPPING[profileAuthType] # map input to API values
37
-
38
- # Auth
39
- authToken = self.ac_login(personalAPIToken, subOrganizationName)
40
-
41
- # Get or create profile
42
- profileId = self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
43
-
44
- # Upload package
45
- self.ac_upload(authToken, appPath, profileId, profileName, message)
46
- end
19
+ createProfileIfNotExists = params[:createProfileIfNotExists]
47
20
 
48
- def self.ac_login(personalAPIToken, subOrganizationName)
49
- begin
50
- token = ''
51
-
52
- user = TDAuthService.get_ac_token(pat: personalAPIToken)
53
- UI.success("Login is successful.")
54
- token = user.accessToken
55
-
56
- if subOrganizationName
57
- organization_id = TDAuthService.get_organization_id(access_token: token, name: subOrganizationName)
58
- user = TDAuthService.get_ac_token(pat: personalAPIToken, sub_organization_id: organization_id)
59
- UI.message("Switched to sub-organization: #{subOrganizationName}")
60
- token = user.accessToken
61
- end
62
-
63
- return token
21
+ valid_extensions = ['.apk', '.aab', '.ipa', '.zip']
64
22
 
65
- rescue => e
66
- UI.user_error!("Login failed: \"#{e.message}\".")
23
+ file_extension = File.extname(appPath).downcase
24
+ unless valid_extensions.include?(file_extension)
25
+ UI.user_error!("Invalid file extension: #{file_extension}. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive).")
67
26
  end
68
- end
69
27
 
70
- def self.ac_get_or_create_profile(authToken, profileName, createProfileIfNotExists, profileCreationSettings, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
71
- begin
72
- profileId = TDUploadService.get_profile_id(authToken, profileName)
28
+ if personalAPIToken.nil? && personalAccessKey.nil?
29
+ UI.user_error!("Either Personal API Token or Personal Access Key is required to authenticate connections to Appcircle services. Please provide a valid access token or access key")
30
+ elsif !personalAPIToken.nil? && !personalAccessKey.nil?
31
+ UI.user_error!("Personal API Token and Personal Access Key cannot be used together. Please provide only one authentication method")
32
+ elsif profileName.nil?
33
+ UI.user_error!("Distribution profile name is required to distribute applications. Please provide a distribution profile name")
34
+ elsif appPath.nil?
35
+ UI.user_error!("Application file path is required to distribute applications. Please provide a valid application file path")
36
+ elsif message.nil?
37
+ UI.user_error!("Message field is required. Please provide a valid message")
38
+ end
73
39
 
74
- if profileId
75
- UI.message("Profile '#{profileName}' found with ID: #{profileId}.")
76
- UI.important("Warning: Profile '#{profileName}' already exists, so the provided profile creation settings will be ignored. To update the profile settings, please use the Appcircle web interface.") if profileCreationSettings
77
40
 
78
- elsif profileId.nil? && !createProfileIfNotExists
79
- UI.user_error!("Error: Profile '#{profileName}' not found. The option 'createProfileIfNotExists' is set to false, so a new profile was not created. To automatically create a new profile when it doesn't exist, set 'createProfileIfNotExists' to true.")
80
- elsif profileId.nil? && createProfileIfNotExists
81
- UI.message("Profile '#{profileName}' not found. Creating the new profile...")
82
- profileId = TDUploadService.create_profile(authToken, profileName, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
83
- end
41
+ authToken = self.ac_login(personalAPIToken, personalAccessKey)
84
42
 
85
- return profileId
86
-
87
- rescue => e
88
- UI.user_error!("Couldn't get the profile: \"#{e.message}\".")
89
- end
43
+ profileId = TDUploadService.get_profile_id(authToken, profileName, createProfileIfNotExists)
44
+ self.ac_upload(authToken, appPath, profileId, message)
90
45
  end
91
46
 
92
- def self.ac_upload(token, appPath, profileID, profileName, message)
47
+ def self.ac_login(personalAPIToken, personalAccessKey)
93
48
  begin
94
- UI.message("Upload started.")
95
- response = TDUploadService.upload_artifact(token: token, message: message, app: appPath, dist_profile_id: profileID)
96
- result = self.checkTaskStatus(token, response['taskId'])
97
-
98
- if result
99
- UI.success("#{appPath} uploaded to profile '#{profileName}' successfully 🎉")
49
+ if personalAccessKey
50
+ user = TDAuthService.get_ac_token_with_personal_access_key(personal_access_key: personalAccessKey)
51
+ else
52
+ user = TDAuthService.get_ac_token(pat: personalAPIToken)
100
53
  end
54
+ UI.success("Login is successful.")
55
+ return user.accessToken
101
56
  rescue => e
102
- status_code = e.respond_to?(:response) && e.response ? e.response.code : 'unknown'
103
- UI.user_error!("Upload failed with status code '#{status_code}', with message \"#{e.message}\".")
57
+ UI.user_error!("Login failed: #{e.message}")
104
58
  end
105
59
  end
106
-
60
+
107
61
  def self.checkTaskStatus(authToken, taskId)
108
62
  uri = URI.parse("https://api.appcircle.io/task/v1/tasks/#{taskId}")
63
+ timeout = 1
109
64
 
110
- check_interval = 1
111
- # timeout = 2 * 60 * 60 # 2 hours in seconds
112
- # start_time = Time.now
113
-
114
- loop do
115
- response = self.send_request(uri, authToken)
116
- if response.is_a?(Net::HTTPSuccess)
117
- stateValue = JSON.parse(response.body)["stateValue"]
118
-
119
- if stateValue == 1
120
- sleep(check_interval)
121
- elsif stateValue == 3
122
- return true
123
- else
124
- UI.error("Task Id #{taskId} failed with state value #{stateValue}.")
125
- UI.user_error!("Upload could not be completed successfully.")
126
- end
65
+ response = self.send_request(uri, authToken)
66
+ if response.is_a?(Net::HTTPSuccess)
67
+ stateValue = JSON.parse(response.body)["stateValue"]
68
+
69
+ if stateValue == 1
70
+ sleep(1)
71
+ return checkTaskStatus(authToken, taskId)
72
+ end
73
+ if stateValue == 3
74
+ return true
127
75
  else
128
- UI.user_error!("Upload failed with response code #{response.code} and message '#{response.message}'.")
76
+ UI.error("Task Id #{taskId} failed with state value #{stateValue}")
77
+ raise "Upload could not completed successfully"
129
78
  end
130
-
131
- # if Time.now - start_time > timeout
132
- # UI.user_error!("Task Id #{taskId} timed out after 2 hours.")
133
- # end
79
+ else
80
+ raise "Upload failed with response code #{response.code} and message '#{response.message}'"
134
81
  end
135
82
  end
136
83
 
@@ -142,6 +89,20 @@ module Fastlane
142
89
  http.request(request)
143
90
  end
144
91
 
92
+ def self.ac_upload(token, appPath, profileID, message)
93
+ begin
94
+ response = TDUploadService.upload_artifact(token: token, message: message, app: appPath, dist_profile_id: profileID)
95
+ result = self.checkTaskStatus(token, response['taskId'])
96
+
97
+ if result
98
+ UI.success("#{appPath} Uploaded to profile id #{profileID} successfully 🎉")
99
+ end
100
+ rescue => e
101
+ status_code = e.respond_to?(:response) && e.response ? e.response.code : 'unknown'
102
+ UI.user_error!("Upload failed with status code #{status_code}, with message '#{e.message}'")
103
+ end
104
+ end
105
+
145
106
  def self.description
146
107
  "Efficiently distribute application builds to users or testing groups using Appcircle's robust platform."
147
108
  end
@@ -156,81 +117,46 @@ module Fastlane
156
117
 
157
118
  def self.details
158
119
  # Optional:
159
- "Appcircle simplifies the distribution of builds to test teams with an extensive platform for managing and tracking applications, versions, testers, and teams. Appcircle integrates with enterprise authentication mechanisms such as LDAP and SSO, ensuring secure distribution of testing packages. Learn more about Appcircle testing distribution."
120
+ "Appcircle simplifies the distribution of builds to test teams with an extensive platform for managing and tracking applications, versions, testers, and teams. Appcircle integrates with enterprise authentication mechanisms such as LDAP and SSO, ensuring secure distribution of testing packages. Learn more about Appcircle testing distribution"
160
121
  end
161
122
 
162
123
  def self.available_options
163
124
  [
164
125
  FastlaneCore::ConfigItem.new(key: :personalAPIToken,
165
- description: "Provide Personal API Token to authenticate connections to Appcircle services",
166
- optional: false,
167
- type: String,
168
- verify_block: proc do |value|
169
- UI.user_error!("Personal API Token cannot be empty. Please provide a valid access token.") unless value && !value.empty?
170
- end),
171
-
172
- FastlaneCore::ConfigItem.new(key: :subOrganizationName,
173
- description: "Optional: Sub-organization name for app distribution. Profiles will be created under root organization if not provided",
126
+ env_name: "AC_PERSONAL_API_TOKEN",
127
+ description: "Provide Personal API Token to authenticate connections to Appcircle services (alternative to personalAccessKey)",
174
128
  optional: true,
175
129
  type: String),
176
-
130
+
131
+ FastlaneCore::ConfigItem.new(key: :personalAccessKey,
132
+ env_name: "AC_PERSONAL_ACCESS_KEY",
133
+ description: "Provide Personal Access Key to authenticate connections to Appcircle services (alternative to personalAPIToken)",
134
+ optional: true,
135
+ type: String),
136
+
177
137
  FastlaneCore::ConfigItem.new(key: :profileName,
178
- description: "Enter the profile name of the Appcircle testing distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
138
+ env_name: "AC_PROFILE_NAME",
139
+ description: "Enter the profile name of the Appcircle distribution profile. This name uniquely identifies the profile under which your applications will be distributed",
179
140
  optional: false,
180
- type: String,
181
- verify_block: proc do |value|
182
- UI.user_error!("Profile name cannot be empty. Please provide a testing distribution profile name.") unless value && !value.empty?
183
- end),
184
-
141
+ type: String),
142
+
185
143
  FastlaneCore::ConfigItem.new(key: :createProfileIfNotExists,
186
- description: "Optional: If the profile does not exist, create a new profile with the given name",
144
+ env_name: "AC_CREATE_PROFILE_IF_NOT_EXISTS",
145
+ description: "If the profile does not exist, create a new profile with the given name",
187
146
  optional: true,
188
147
  type: Boolean),
189
148
 
190
- FastlaneCore::ConfigItem.new(key: :profileCreationSettings,
191
- description: "Optional: Profile creation settings for the testing distribution profile",
192
- optional: true,
193
- type: Hash,
194
- verify_block: proc do |value|
195
- # Parse and Validate
196
- if value[:authType] && !value[:authType].empty?
197
- UI.user_error!("Invalid authType: '#{value[:authType]}'. Options: 'none' (None), 'static' (Static Username and Password), 'ldap' (LDAP Login), 'sso' (SSO Login).") unless AUTH_TYPE_MAPPING.key?(value[:authType])
198
-
199
- if value[:authType] == 'static'
200
- UI.user_error!("username must be a String and at least 6 characters long.") unless value[:username].kind_of?(String) && value[:username].length >= 6
201
- UI.user_error!("password must be a String and at least 6 characters long.") unless value[:password].kind_of?(String) && value[:password].length >= 6
202
- else
203
- value[:username] = nil
204
- value[:password] = nil
205
- end
206
- end
207
-
208
- if value[:testingGroupNames] && !value[:testingGroupNames].empty?
209
- value[:testingGroupNames] = value[:testingGroupNames].to_s.split(",").map(&:strip)
210
- UI.user_error!("testingGroupNames must be a string array. Ex: 'group1, group2, group3'.") unless value[:testingGroupNames].kind_of?(Array)
211
- end
212
- end),
213
-
214
149
  FastlaneCore::ConfigItem.new(key: :appPath,
150
+ env_name: "AC_APP_PATH",
215
151
  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",
216
152
  optional: false,
217
- type: String,
218
- verify_block: proc do |value|
219
- UI.user_error!("Application file path cannot be empty. Please provide a valid application file path.") unless value && !value.empty?
220
-
221
- file_extension = File.extname(value).downcase
222
- unless VALID_EXTENSIONS.include?(file_extension)
223
- UI.user_error!("Invalid file extension: '#{file_extension}'. For Android, use .apk or .aab. For iOS, use .ipa or .zip(.xcarchive).")
224
- end
225
- end),
153
+ type: String),
226
154
 
227
155
  FastlaneCore::ConfigItem.new(key: :message,
228
- description: "Message to include with the distribution to provide additional information to testers or users receiving the build",
156
+ env_name: "AC_MESSAGE",
157
+ description: "Optional message to include with the distribution to provide additional information to testers or users receiving the build",
229
158
  optional: false,
230
- type: String,
231
- verify_block: proc do |value|
232
- UI.user_error!("Message field cannot be empty. Please provide a message.") unless value && !value.empty?
233
- end)
159
+ type: String)
234
160
  ]
235
161
  end
236
162
 
@@ -13,7 +13,7 @@ class UserResponse
13
13
  end
14
14
 
15
15
  module TDAuthService
16
- def self.get_ac_token(pat:, sub_organization_id: nil)
16
+ def self.get_ac_token(pat:)
17
17
  endpoint_url = 'https://auth.appcircle.io/auth/v2/token'
18
18
  uri = URI(endpoint_url)
19
19
 
@@ -24,7 +24,6 @@ module TDAuthService
24
24
 
25
25
  # Encode parameters
26
26
  params = { pat: pat }
27
- params[:subOrganizationId] = sub_organization_id if sub_organization_id
28
27
  request.body = URI.encode_www_form(params)
29
28
 
30
29
  # Make the HTTP request
@@ -32,6 +31,8 @@ module TDAuthService
32
31
  http.request(request)
33
32
  end
34
33
 
34
+
35
+
35
36
  # Check response
36
37
  if response.is_a?(Net::HTTPSuccess)
37
38
  response_data = JSON.parse(response.body)
@@ -42,35 +43,39 @@ module TDAuthService
42
43
 
43
44
  return user
44
45
  else
45
- raise "Error: (#{response.code} #{response.message})."
46
+ raise "HTTP Request failed (#{response.code} #{response.message})"
46
47
  end
47
48
  end
48
49
 
49
- def self.get_organization_id(access_token:, name:)
50
- endpoint_url = 'https://api.appcircle.io/identity/v1/organizations'
50
+ def self.get_ac_token_with_personal_access_key(personal_access_key:)
51
+ endpoint_url = 'https://auth.appcircle.io/auth/v1/token'
51
52
  uri = URI(endpoint_url)
52
-
53
+
53
54
  # Create HTTP request
54
- request = Net::HTTP::Get.new(uri)
55
- request['Authorization'] = "Bearer #{access_token}"
55
+ request = Net::HTTP::Post.new(uri)
56
+ request.content_type = 'application/x-www-form-urlencoded'
56
57
  request['Accept'] = 'application/json'
57
-
58
+
59
+ # Encode parameters
60
+ params = { 'personal-access-key' => personal_access_key }
61
+ request.body = URI.encode_www_form(params)
62
+
58
63
  # Make the HTTP request
59
64
  response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
60
65
  http.request(request)
61
66
  end
62
-
67
+
63
68
  # Check response
64
69
  if response.is_a?(Net::HTTPSuccess)
65
70
  response_data = JSON.parse(response.body)
66
- organizations = response_data['data']
67
- organization = organizations.find { |org| org['name'] == name }
68
-
69
- raise "Organization with name '#{name}' not found" unless organization
70
- return organization['id']
71
71
 
72
+ user = UserResponse.new(
73
+ accessToken: response_data['access_token']
74
+ )
75
+
76
+ return user
72
77
  else
73
- raise "Error: (#{response.code} #{response.message})"
78
+ raise "HTTP Request failed (#{response.code} #{response.message})"
74
79
  end
75
80
  end
76
81
  end
@@ -9,12 +9,12 @@ module TDUploadService
9
9
  def self.upload_artifact(token:, message:, app:, dist_profile_id:)
10
10
  url = "https://api.appcircle.io/distribution/v2/profiles/#{dist_profile_id}/app-versions"
11
11
  headers = {
12
- Authorization: "Bearer #{token}"
12
+ Authorization: "Bearer #{token}",
13
+ content_type: :multipart # multipart/form-data
13
14
  }
14
15
  payload = {
15
16
  Message: message,
16
- File: File.new(app, 'rb'),
17
- multipart: true # Force multipart encoding for RestClient
17
+ File: File.new(app, 'rb')
18
18
  }
19
19
 
20
20
  begin
@@ -38,26 +38,9 @@ module TDUploadService
38
38
 
39
39
  begin
40
40
  response = RestClient.get(url, headers)
41
- JSON.parse(response.body)
42
- rescue RestClient::ExceptionWithResponse => e
43
- raise e
44
- rescue StandardError => e
45
- raise e
46
- end
47
- end
48
-
49
- def self.get_testing_groups(auth_token:)
50
- url = "#{BASE_URL}/distribution/v2/testing-groups"
41
+ parsed_response = JSON.parse(response.body)
51
42
 
52
- # Set up the headers with authentication
53
- headers = {
54
- Authorization: "Bearer #{auth_token}",
55
- accept: 'application/json'
56
- }
57
-
58
- begin
59
- response = RestClient.get(url, headers)
60
- JSON.parse(response.body)
43
+ parsed_response
61
44
  rescue RestClient::ExceptionWithResponse => e
62
45
  raise e
63
46
  rescue StandardError => e
@@ -86,40 +69,7 @@ module TDUploadService
86
69
  end
87
70
  end
88
71
 
89
- def self.update_distribution_profile(profile_id:, auth_type:, username:, password:, testing_group_ids:, auth_token:)
90
- url = "#{BASE_URL}/distribution/v2/profiles/#{profile_id}"
91
- headers = {
92
- Authorization: "Bearer #{auth_token}",
93
- content_type: :json,
94
- accept: 'application/json-patch+json'
95
- }
96
-
97
- ### Construct the payload
98
- payload = {}
99
-
100
- settings_payload = {
101
- authenticationType: auth_type,
102
- username: username,
103
- password: password
104
- }.compact
105
-
106
- payload[:settings] = settings_payload unless settings_payload.empty?
107
- payload[:testingGroupIds] = testing_group_ids unless testing_group_ids&.empty?
108
-
109
- payload = payload.compact.to_json
110
- ###
111
-
112
- begin
113
- response = RestClient.patch(url, payload, headers)
114
- JSON.parse(response.body)
115
- rescue RestClient::ExceptionWithResponse => e
116
- raise e
117
- rescue StandardError => e
118
- raise e
119
- end
120
- end
121
-
122
- def self.get_profile_id(authToken, profileName)
72
+ def self.get_profile_id(authToken, profileName, createProfileIfNotExists)
123
73
  profileId = nil
124
74
 
125
75
  begin
@@ -127,75 +77,27 @@ module TDUploadService
127
77
  profiles.each do |profile|
128
78
  if profile["name"] == profileName
129
79
  profileId = profile['id']
130
- break
131
- end
132
- end
133
- rescue => e
134
- raise "Something went wrong while fetching profiles: #{e.message}."
135
- end
136
-
137
- return profileId
138
- end
139
-
140
- def self.get_testing_group_ids(authToken, testingGroupNames)
141
- testingGroupIds = []
142
- remainingGroupNames = Set.new(testingGroupNames)
143
-
144
- begin
145
- groups = TDUploadService.get_testing_groups(auth_token: authToken)
146
-
147
- groups.each do |group|
148
- if remainingGroupNames.include?(group["name"])
149
- testingGroupIds.push(group['id'])
150
- remainingGroupNames.delete(group["name"])
151
80
  end
152
81
  end
153
82
  rescue => e
154
- raise "Something went wrong while fetching testing groups: #{e.message}."
155
- end
156
-
157
- raise "Following testing groups couldn't be found: '#{remainingGroupNames.to_a.join(', ')}'. Aborting profile creation..." unless remainingGroupNames.empty?
158
-
159
- return testingGroupIds
160
- end
161
-
162
- def self.create_profile(authToken, profileName, profileAuthType, profileUsername, profilePassword, profileTestingGroupNames)
163
- # Get testing group IDs
164
- if !profileTestingGroupNames&.empty?
165
- profileTestingGroupIds = TDUploadService.get_testing_group_ids(authToken, profileTestingGroupNames)
83
+ raise "Something went wrong while fetching profiles: #{e.message}"
166
84
  end
167
-
168
- # Create
169
- begin
170
- new_profile = TDUploadService.create_distribution_profile(
171
- name: profileName,
172
- auth_token: authToken
173
- )
174
- if new_profile.nil?
175
- raise "Error: The new profile could not be created."
176
- end
177
- profileId = new_profile['id']
178
- rescue => e
179
- raise "Something went wrong while creating a new profile: #{e.message}."
85
+
86
+ if profileId.nil? && !createProfileIfNotExists
87
+ raise "Error: The test profile '#{profileName}' could not be found. The option 'createProfileIfNotExists' is set to false, so no new profile was created. To automatically create a new profile if it doesn't exist, set 'createProfileIfNotExists' to true."
180
88
  end
181
89
 
182
- # Configure
183
- begin
184
- Fastlane::UI.message("Configuring the profile...")
185
- configured_profile = TDUploadService.update_distribution_profile(
186
- profile_id: profileId,
187
- auth_type: profileAuthType,
188
- username: profileUsername,
189
- password: profilePassword,
190
- testing_group_ids: profileTestingGroupIds,
191
- auth_token: authToken
192
- )
193
- if configured_profile.nil?
194
- raise "Error: The new profile could not be configured."
90
+ if profileId.nil? && createProfileIfNotExists
91
+ begin
92
+ puts "The test profile '#{profileName}' could not be found. A new profile is being created..."
93
+ new_profile = TDUploadService.create_distribution_profile(name: profileName, auth_token: authToken)
94
+ if new_profile.nil?
95
+ raise "Error: The new profile could not be created."
96
+ end
97
+ profileId = new_profile['id']
98
+ rescue => e
99
+ raise "Something went wrong while creating a new profile: #{e.message}"
195
100
  end
196
- profileId = configured_profile['id'] # Should be the same as before
197
- rescue => e
198
- raise "Something went wrong while configuring the new profile: #{e.message}."
199
101
  end
200
102
 
201
103
  return profileId
@@ -8,6 +8,9 @@ module Fastlane
8
8
  # class methods that you define here become available in your action
9
9
  # as `Helper::AppcircleTestingDistributionHelper.your_method`
10
10
  #
11
+ def self.show_message
12
+ UI.message("Hello from the appcircle_testing_distribution plugin helper!")
13
+ end
11
14
  end
12
15
  end
13
16
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module AppcircleTestingDistribution
3
- VERSION = "0.4.0"
3
+ VERSION = "0.4.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_testing_distribution
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - appcircleio
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-12-18 00:00:00.000000000 Z
11
+ date: 2025-11-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rest-client
@@ -32,6 +32,8 @@ extra_rdoc_files: []
32
32
  files:
33
33
  - LICENSE
34
34
  - README.md
35
+ - images/PAT.png
36
+ - images/distribution-start.png
35
37
  - images/extension-icon.png
36
38
  - lib/fastlane/plugin/appcircle_testing_distribution.rb
37
39
  - lib/fastlane/plugin/appcircle_testing_distribution/actions/appcircle_testing_distribution_action.rb
@@ -43,7 +45,7 @@ homepage: https://github.com/appcircleio/fastlane_plugin_appcircle_testing_distr
43
45
  licenses:
44
46
  - MIT
45
47
  metadata:
46
- rubygems_mfa_required: 'false'
48
+ rubygems_mfa_required: 'true'
47
49
  post_install_message:
48
50
  rdoc_options: []
49
51
  require_paths:
@@ -59,7 +61,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
61
  - !ruby/object:Gem::Version
60
62
  version: '0'
61
63
  requirements: []
62
- rubygems_version: 3.4.19
64
+ rubygems_version: 3.4.10
63
65
  signing_key:
64
66
  specification_version: 4
65
67
  summary: Efficiently distribute application builds to users or testing groups using