fastlane-plugin-pcloudy 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 31b41a535d0d2a935e7bd9df3d37c97260491f3bcbc758cc62d473e762f34969
4
+ data.tar.gz: e8861e28863669b33b4a52049fa738014412db6aed0839b8df268c0ea77a7a46
5
+ SHA512:
6
+ metadata.gz: 2edbd581c9ada9dba62432426856447ba32173489260fb97767a8007365e992a21ec316c7b917ed5906f8745317df8cab3687793a8092f89cd84f8a4fa161fa5
7
+ data.tar.gz: 6d92f4f62758cf0745779161f70e88c520b07ef63be2da5632002c26dcfd43b43872b75d42ca519d4130770d1c8a0e5c7a0c689882c7ab92b38c4e16ea2eb9c3
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0
4
+
5
+ First release.
6
+
7
+ - `pcloudy_list_devices`: lists real iOS and Android devices on pCloudy, with filters for
8
+ platform, availability, OS version, manufacturer and device name. Prints a table, returns
9
+ the devices, and stores them in `lane_context[SharedValues::PCLOUDY_DEVICES]`.
10
+ - Supports device.pcloudy.com and private clouds through `cloud_url` / `PCLOUDY_CLOUD_URL`.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Smart Software Testing Solutions
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 all
13
+ 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 THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # fastlane-plugin-pcloudy
2
+
3
+ A [fastlane](https://fastlane.tools) plugin for [pCloudy](https://www.pcloudy.com). It lets your fastlane lanes work with real iOS and Android devices on the pCloudy device cloud.
4
+
5
+ | Action | Status |
6
+ |---|---|
7
+ | `pcloudy_list_devices`: list available devices | ✅ Available |
8
+ | `pcloudy_upload_app`: upload an `.ipa` / `.apk` | Planned |
9
+ | `pcloudy_resign_app`: resign an `.ipa` for pCloudy devices | Planned |
10
+ | `pcloudy_book_device` / `pcloudy_release_device` | Planned |
11
+ | `pcloudy_install_app`: install and launch on a booked device | Planned |
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ fastlane add_plugin pcloudy
17
+ ```
18
+
19
+ This adds `gem "fastlane-plugin-pcloudy"` to your project's `fastlane/Pluginfile`.
20
+
21
+ ## Credentials
22
+
23
+ Put your pCloudy credentials in `fastlane/.env`, and keep that file out of git:
24
+
25
+ ```
26
+ PCLOUDY_USERNAME=you@company.com
27
+ PCLOUDY_API_KEY=your-api-key # pCloudy > Settings > API Key
28
+ # PCLOUDY_CLOUD_URL=https://device.pcloudy.com # optional, for private clouds
29
+ ```
30
+
31
+ ## `pcloudy_list_devices`
32
+
33
+ ```ruby
34
+ pcloudy_list_devices # all available devices
35
+ pcloudy_list_devices(platform: "ios", os_version: "17") # iOS 17.x only
36
+ devices = pcloudy_list_devices(platform: "android",
37
+ manufacturer: "Samsung",
38
+ print_table: false)
39
+ UI.message("First device id: #{devices.first['id']}")
40
+ ```
41
+
42
+ | Option | Env var | Default | Description |
43
+ |---|---|---|---|
44
+ | `username` | `PCLOUDY_USERNAME` | — | pCloudy account email |
45
+ | `api_key` | `PCLOUDY_API_KEY` | — | pCloudy API key |
46
+ | `cloud_url` | `PCLOUDY_CLOUD_URL` | `https://device.pcloudy.com` | Cloud URL |
47
+ | `platform` | `PCLOUDY_PLATFORM` | `all` | `ios`, `android` or `all` |
48
+ | `available_now` | `PCLOUDY_AVAILABLE_NOW` | `true` | Only devices that are free now |
49
+ | `duration` | `PCLOUDY_DURATION` | `10` | Minutes, used for the availability check |
50
+ | `os_version` | `PCLOUDY_OS_VERSION` | — | OS version or prefix, e.g. `17` or `17.5` |
51
+ | `manufacturer` | `PCLOUDY_MANUFACTURER` | — | e.g. `Apple`, `Samsung` |
52
+ | `name` | `PCLOUDY_DEVICE_NAME` | — | Text in the device name, e.g. `iPhone 15` |
53
+ | `print_table` | — | `true` | Print a table of the results |
54
+
55
+ The action returns an array of device hashes (`id`, `full_name`, `display_name`, `model`, `manufacturer`, `platform`, `version`, `available`, …). It also stores the array in `lane_context[SharedValues::PCLOUDY_DEVICES]`.
56
+
57
+ ## Development
58
+
59
+ ```bash
60
+ bundle install
61
+ bundle exec rspec # unit tests (HTTP is stubbed)
62
+ bundle exec fastlane list_devices platform:ios # live run, needs fastlane/.env
63
+ ```
@@ -0,0 +1,182 @@
1
+ require "fastlane/action"
2
+ require "terminal-table"
3
+ require_relative "../helper/pcloudy_client"
4
+
5
+ module Fastlane
6
+ module Actions
7
+ module SharedValues
8
+ PCLOUDY_DEVICES = :PCLOUDY_DEVICES
9
+ end
10
+
11
+ class PcloudyListDevicesAction < Action
12
+ PLATFORMS = %w(ios android all).freeze
13
+
14
+ def self.run(params)
15
+ client = Helper::PcloudyClient.new(username: params[:username],
16
+ api_key: params[:api_key],
17
+ cloud_url: params[:cloud_url])
18
+
19
+ platforms = params[:platform] == "all" ? %w(ios android) : [params[:platform]]
20
+ devices = platforms.flat_map do |platform|
21
+ client.devices(platform: platform,
22
+ duration: params[:duration],
23
+ available_now: params[:available_now])
24
+ end
25
+
26
+ devices = filter(devices, params)
27
+
28
+ print_table(devices, params) if params[:print_table]
29
+ UI.success("Found #{devices.count} pCloudy device(s)")
30
+
31
+ Actions.lane_context[SharedValues::PCLOUDY_DEVICES] = devices
32
+ devices
33
+ rescue Helper::PcloudyClient::Error => e
34
+ UI.user_error!(e.message)
35
+ end
36
+
37
+ def self.filter(devices, params)
38
+ devices.select do |device|
39
+ matches_version?(device["version"], params[:os_version]) &&
40
+ matches_text?(device["manufacturer"], params[:manufacturer]) &&
41
+ matches_name?(device, params[:name])
42
+ end
43
+ end
44
+
45
+ # "17" matches 17.5.1 and 17.0, but not 1.7 or 170.0
46
+ def self.matches_version?(version, wanted)
47
+ return true if wanted.to_s.empty?
48
+
49
+ version = version.to_s
50
+ version == wanted || version.start_with?("#{wanted}.")
51
+ end
52
+
53
+ def self.matches_text?(value, wanted)
54
+ wanted.to_s.empty? || value.to_s.casecmp?(wanted.to_s)
55
+ end
56
+
57
+ def self.matches_name?(device, wanted)
58
+ return true if wanted.to_s.empty?
59
+
60
+ needle = wanted.to_s.downcase.delete(" ")
61
+ [device["full_name"], device["display_name"], device["model"]].any? do |value|
62
+ value.to_s.downcase.delete(" ").include?(needle)
63
+ end
64
+ end
65
+
66
+ def self.print_table(devices, params)
67
+ if devices.empty?
68
+ UI.important("No pCloudy devices matched your filters")
69
+ return
70
+ end
71
+
72
+ rows = devices.map do |d|
73
+ [d["id"], d["display_name"], d["manufacturer"], d["platform"], d["version"], d["available"] ? "yes" : "no"]
74
+ end
75
+ title = "pCloudy devices (#{params[:platform]}#{params[:available_now] ? ', available now' : ''})"
76
+ puts(Terminal::Table.new(title: title,
77
+ headings: ["ID", "Device", "Manufacturer", "Platform", "OS", "Available"],
78
+ rows: rows))
79
+ end
80
+
81
+ #####################################################
82
+ # @!group Documentation
83
+ #####################################################
84
+
85
+ def self.description
86
+ "Lists real iOS and Android devices on the pCloudy device cloud"
87
+ end
88
+
89
+ def self.details
90
+ "Authenticates with your pCloudy username and API key, fetches the device list, " \
91
+ "optionally filters it by OS version, manufacturer or name, prints a table and returns the devices."
92
+ end
93
+
94
+ def self.authors
95
+ ["bikee1235"]
96
+ end
97
+
98
+ def self.return_value
99
+ "Array of device hashes as returned by pCloudy (id, full_name, display_name, model, manufacturer, platform, version, available, ...)"
100
+ end
101
+
102
+ def self.output
103
+ [["PCLOUDY_DEVICES", "Array of the devices that matched"]]
104
+ end
105
+
106
+ def self.available_options
107
+ [
108
+ FastlaneCore::ConfigItem.new(key: :username,
109
+ env_name: "PCLOUDY_USERNAME",
110
+ description: "pCloudy account email",
111
+ optional: false,
112
+ type: String),
113
+ FastlaneCore::ConfigItem.new(key: :api_key,
114
+ env_name: "PCLOUDY_API_KEY",
115
+ description: "pCloudy API key (Settings > API Key)",
116
+ optional: false,
117
+ sensitive: true,
118
+ code_gen_sensitive: true,
119
+ type: String),
120
+ FastlaneCore::ConfigItem.new(key: :cloud_url,
121
+ env_name: "PCLOUDY_CLOUD_URL",
122
+ description: "pCloudy cloud URL (change for private clouds)",
123
+ default_value: Helper::PcloudyClient::DEFAULT_CLOUD_URL,
124
+ type: String),
125
+ FastlaneCore::ConfigItem.new(key: :platform,
126
+ env_name: "PCLOUDY_PLATFORM",
127
+ description: "Device platform: ios, android or all",
128
+ default_value: "all",
129
+ type: String,
130
+ verify_block: proc do |value|
131
+ UI.user_error!("platform must be one of #{PLATFORMS.join(', ')}") unless PLATFORMS.include?(value)
132
+ end),
133
+ FastlaneCore::ConfigItem.new(key: :available_now,
134
+ env_name: "PCLOUDY_AVAILABLE_NOW",
135
+ description: "Only list devices that are free right now",
136
+ default_value: true,
137
+ type: Boolean),
138
+ FastlaneCore::ConfigItem.new(key: :duration,
139
+ env_name: "PCLOUDY_DURATION",
140
+ description: "Booking duration in minutes used for the availability check",
141
+ default_value: 10,
142
+ type: Integer),
143
+ FastlaneCore::ConfigItem.new(key: :os_version,
144
+ env_name: "PCLOUDY_OS_VERSION",
145
+ description: "Only devices with this OS version or prefix, e.g. \"17\" or \"17.5\"",
146
+ optional: true,
147
+ type: String),
148
+ FastlaneCore::ConfigItem.new(key: :manufacturer,
149
+ env_name: "PCLOUDY_MANUFACTURER",
150
+ description: "Only devices from this manufacturer, e.g. Apple, Samsung",
151
+ optional: true,
152
+ type: String),
153
+ FastlaneCore::ConfigItem.new(key: :name,
154
+ env_name: "PCLOUDY_DEVICE_NAME",
155
+ description: "Only devices whose name contains this text, e.g. \"iPhone 15\"",
156
+ optional: true,
157
+ type: String),
158
+ FastlaneCore::ConfigItem.new(key: :print_table,
159
+ description: "Print the devices as a table",
160
+ default_value: true,
161
+ type: Boolean)
162
+ ]
163
+ end
164
+
165
+ def self.example_code
166
+ [
167
+ 'pcloudy_list_devices',
168
+ 'pcloudy_list_devices(platform: "ios", os_version: "17")',
169
+ 'devices = pcloudy_list_devices(platform: "android", manufacturer: "Samsung", print_table: false)'
170
+ ]
171
+ end
172
+
173
+ def self.category
174
+ :testing
175
+ end
176
+
177
+ def self.is_supported?(platform)
178
+ [:ios, :android].include?(platform)
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,87 @@
1
+ require "json"
2
+ require "net/http"
3
+ require "uri"
4
+
5
+ module Fastlane
6
+ module Helper
7
+ # Thin wrapper around the pCloudy REST API.
8
+ # Every pCloudy response has the shape {"result": {"code": 200, ...}}.
9
+ class PcloudyClient
10
+ class Error < StandardError; end
11
+
12
+ DEFAULT_CLOUD_URL = "https://device.pcloudy.com".freeze
13
+
14
+ def initialize(username:, api_key:, cloud_url: DEFAULT_CLOUD_URL, timeout: 60)
15
+ @username = username
16
+ @api_key = api_key
17
+ @timeout = timeout
18
+ @base = URI(normalize_url(cloud_url))
19
+ end
20
+
21
+ # GET /api/access with basic auth (email:api_key). The token is cached per client.
22
+ def token
23
+ @token ||= begin
24
+ request = Net::HTTP::Get.new(endpoint("/api/access"))
25
+ request.basic_auth(@username, @api_key)
26
+ result = perform(request)
27
+ result["token"] || raise(Error, "pCloudy authentication returned no token")
28
+ end
29
+ end
30
+
31
+ # POST /api/devices. Returns an array of device hashes (string keys), e.g.
32
+ # {"id" => 827, "full_name" => "Apple_iPhone11Pro_Ios_17.5.1_45645", "platform" => "ios", "version" => "17.5.1", ...}
33
+ def devices(platform:, duration: 10, available_now: true)
34
+ result = post_json("/api/devices",
35
+ token: token,
36
+ duration: duration,
37
+ platform: platform,
38
+ available_now: available_now.to_s)
39
+ result["models"] || []
40
+ end
41
+
42
+ private
43
+
44
+ def post_json(path, payload)
45
+ request = Net::HTTP::Post.new(endpoint(path), "Content-Type" => "application/json")
46
+ request.body = JSON.generate(payload)
47
+ perform(request)
48
+ end
49
+
50
+ def perform(request)
51
+ response = Net::HTTP.start(@base.host, @base.port,
52
+ use_ssl: @base.scheme == "https",
53
+ open_timeout: @timeout,
54
+ read_timeout: @timeout) do |http|
55
+ http.request(request)
56
+ end
57
+
58
+ path = request.path
59
+ raise(Error, "pCloudy #{path} returned HTTP #{response.code}") unless response.is_a?(Net::HTTPSuccess)
60
+
61
+ result = JSON.parse(response.body.to_s)["result"]
62
+ raise(Error, "pCloudy #{path} returned an unexpected response") unless result.kind_of?(Hash)
63
+
64
+ unless result["code"].to_i == 200
65
+ message = result["error"] || result["msg"] || result["message"] || "unknown error"
66
+ raise(Error, "pCloudy #{path} failed (code #{result['code']}): #{message}")
67
+ end
68
+
69
+ result
70
+ rescue JSON::ParserError
71
+ raise(Error, "pCloudy #{request.path} returned invalid JSON")
72
+ rescue SocketError, Timeout::Error, Errno::ECONNREFUSED, Errno::ECONNRESET, OpenSSL::SSL::SSLError => e
73
+ raise(Error, "Could not reach pCloudy at #{@base}: #{e.message}")
74
+ end
75
+
76
+ def endpoint(path)
77
+ URI.join(@base.to_s.end_with?("/") ? @base.to_s : "#{@base}/", path.sub(%r{^/}, ""))
78
+ end
79
+
80
+ def normalize_url(url)
81
+ url = url.to_s.strip
82
+ url = "https://#{url}" unless url =~ %r{^https?://}i
83
+ url.chomp("/")
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ module Fastlane
2
+ module Pcloudy
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ require "fastlane/plugin/pcloudy/version"
2
+
3
+ module Fastlane
4
+ module Pcloudy
5
+ # Return all .rb files inside the "actions" and "helper" directory
6
+ def self.all_classes
7
+ Dir[File.expand_path("**/{actions,helper}/*.rb", File.dirname(__FILE__))]
8
+ end
9
+ end
10
+ end
11
+
12
+ # By default we want to import all available actions and helpers
13
+ # A plugin can contain any number of actions and plugins
14
+ Fastlane::Pcloudy.all_classes.each do |current|
15
+ require current
16
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastlane-plugin-pcloudy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - bikee1235
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: bundler
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.17'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '3'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '1.17'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '3'
32
+ - !ruby/object:Gem::Dependency
33
+ name: fastlane
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: 2.220.0
39
+ - - "<"
40
+ - !ruby/object:Gem::Version
41
+ version: '3'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: 2.220.0
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '3'
52
+ - !ruby/object:Gem::Dependency
53
+ name: rake
54
+ requirement: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: '13.0'
59
+ type: :development
60
+ prerelease: false
61
+ version_requirements: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - "~>"
64
+ - !ruby/object:Gem::Version
65
+ version: '13.0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: rspec
68
+ requirement: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: '3.13'
73
+ type: :development
74
+ prerelease: false
75
+ version_requirements: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '3.13'
80
+ - !ruby/object:Gem::Dependency
81
+ name: webmock
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '3.0'
87
+ type: :development
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '3.0'
94
+ description: Lists available real iOS and Android devices on pCloudy (device.pcloudy.com
95
+ or a private cloud) from your fastlane lanes, with filters for platform, OS version,
96
+ manufacturer and device name.
97
+ email:
98
+ - bikee.bihari@sstsinc.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE
105
+ - README.md
106
+ - lib/fastlane/plugin/pcloudy.rb
107
+ - lib/fastlane/plugin/pcloudy/actions/pcloudy_list_devices_action.rb
108
+ - lib/fastlane/plugin/pcloudy/helper/pcloudy_client.rb
109
+ - lib/fastlane/plugin/pcloudy/version.rb
110
+ homepage: https://github.com/Smart-Software-Testing-Solutions-Opkey/fastlane-plugin-pcloudy
111
+ licenses:
112
+ - MIT
113
+ metadata:
114
+ homepage_uri: https://github.com/Smart-Software-Testing-Solutions-Opkey/fastlane-plugin-pcloudy
115
+ changelog_uri: https://github.com/Smart-Software-Testing-Solutions-Opkey/fastlane-plugin-pcloudy/blob/main/CHANGELOG.md
116
+ bug_tracker_uri: https://github.com/Smart-Software-Testing-Solutions-Opkey/fastlane-plugin-pcloudy/issues
117
+ rubygems_mfa_required: 'true'
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '2.6'
126
+ required_rubygems_version: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: '0'
131
+ requirements: []
132
+ rubygems_version: 3.6.9
133
+ specification_version: 4
134
+ summary: 'Fastlane plugin for pCloudy: work with real iOS and Android devices on the
135
+ pCloudy device cloud.'
136
+ test_files: []