fastlane-plugin-pcloudy 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 31b41a535d0d2a935e7bd9df3d37c97260491f3bcbc758cc62d473e762f34969
4
- data.tar.gz: e8861e28863669b33b4a52049fa738014412db6aed0839b8df268c0ea77a7a46
3
+ metadata.gz: 5beb8f759d9c8483fbb1d9185edc83e0d09c6d22e900c4e848ff635adff4f1e7
4
+ data.tar.gz: 4c21078f470ecbf008c417c60a6afac259b84750850b9c6d489048715d8311cd
5
5
  SHA512:
6
- metadata.gz: 2edbd581c9ada9dba62432426856447ba32173489260fb97767a8007365e992a21ec316c7b917ed5906f8745317df8cab3687793a8092f89cd84f8a4fa161fa5
7
- data.tar.gz: 6d92f4f62758cf0745779161f70e88c520b07ef63be2da5632002c26dcfd43b43872b75d42ca519d4130770d1c8a0e5c7a0c689882c7ab92b38c4e16ea2eb9c3
6
+ metadata.gz: baa15536e27dc025e5e56845f256fc35d4c4d4f37b2781a7e8052f946b9649d817caccd2adfaebe233e718d172f812433c103b81419ba51251a5639c9f06e696
7
+ data.tar.gz: 07a4aceb6833f999a52be92af8cd1aaf1b02c6f94ec2408768c70f18576c2b10014ad761c1caac714b55e4e66517f99e94526bf576ed8744fd669a1ae018f472
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.2.0
4
+
5
+ - New `pcloudy_upload_app`: uploads an `.ipa` or `.apk` to pCloudy My Apps. Picks up the output of
6
+ `build_app` / `gradle` automatically, waits until pCloudy has registered the file, and returns the
7
+ name pCloudy stored it under (pCloudy adds a timestamp when the name already exists).
8
+ `resign: true` re-signs an `.ipa` in the same step.
9
+ - New `pcloudy_resign_app`: re-signs an uploaded `.ipa` with pCloudy's certificate and waits for it to finish.
10
+ - New shared values: `PCLOUDY_UPLOADED_FILE`, `PCLOUDY_RESIGNED_FILE`, `PCLOUDY_APP_FILE`.
11
+
3
12
  ## 0.1.0
4
13
 
5
14
  First release.
data/README.md CHANGED
@@ -5,8 +5,8 @@ A [fastlane](https://fastlane.tools) plugin for [pCloudy](https://www.pcloudy.co
5
5
  | Action | Status |
6
6
  |---|---|
7
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 |
8
+ | `pcloudy_upload_app`: upload an `.ipa` / `.apk` | ✅ Available |
9
+ | `pcloudy_resign_app`: re-sign an `.ipa` for pCloudy devices | ✅ Available |
10
10
  | `pcloudy_book_device` / `pcloudy_release_device` | Planned |
11
11
  | `pcloudy_install_app`: install and launch on a booked device | Planned |
12
12
 
@@ -28,6 +28,70 @@ PCLOUDY_API_KEY=your-api-key # pCloudy > Settings > API Key
28
28
  # PCLOUDY_CLOUD_URL=https://device.pcloudy.com # optional, for private clouds
29
29
  ```
30
30
 
31
+ ## Quick start
32
+
33
+ ```ruby
34
+ # iOS: build, upload and re-sign with pCloudy's certificate
35
+ lane :pcloudy_ios do
36
+ build_app(scheme: "MyApp", export_method: "development")
37
+ pcloudy_upload_app(resign: true)
38
+ end
39
+
40
+ # Android: build and upload
41
+ lane :pcloudy_android do
42
+ gradle(task: "assembleDebug")
43
+ pcloudy_upload_app
44
+ end
45
+ ```
46
+
47
+ The app then appears in **pCloudy → My Apps**, ready to install on real devices.
48
+
49
+ ## `pcloudy_upload_app`
50
+
51
+ Uploads an `.ipa` or `.apk` to pCloudy My Apps. If you don't pass `file:`, it uses the `.ipa` from `build_app` or the `.apk` from `gradle`.
52
+
53
+ When a file with the same name already exists, pCloudy saves the upload under a new, timestamped name (e.g. `MyApp-1794551836.ipa`). The action returns that name, so always use the return value or `PCLOUDY_UPLOADED_FILE`, never the local file name.
54
+
55
+ ```ruby
56
+ pcloudy_upload_app # output of build_app / gradle
57
+ pcloudy_upload_app(resign: true) # iOS: upload + re-sign in one step
58
+ name = pcloudy_upload_app(file: "./build/MyApp.apk")
59
+ ```
60
+
61
+ | Option | Env var | Default | Description |
62
+ |---|---|---|---|
63
+ | `file` | `PCLOUDY_APP_PATH` | output of `build_app` / `gradle` | Path to the `.ipa` or `.apk` |
64
+ | `resign` | `PCLOUDY_RESIGN` | `false` | Re-sign the `.ipa` after uploading (iOS only) |
65
+ | `wait_for_processing` | `PCLOUDY_WAIT_FOR_PROCESSING` | `true` | Wait until the file appears in My Apps |
66
+ | `upload_timeout` | `PCLOUDY_UPLOAD_TIMEOUT` | `600` | Seconds allowed for the upload |
67
+ | `processing_timeout` | `PCLOUDY_PROCESSING_TIMEOUT` | `120` | Seconds to wait for the file to appear in My Apps |
68
+ | `resign_timeout` | `PCLOUDY_RESIGN_TIMEOUT` | `600` | Seconds to wait for re-signing (with `resign: true`) |
69
+
70
+ Plus `username`, `api_key` and `cloud_url`, as for every action.
71
+
72
+ **Returns:** the file name in My Apps (the re-signed copy when `resign: true`).
73
+ **Sets:** `PCLOUDY_UPLOADED_FILE`, `PCLOUDY_APP_FILE`, and with `resign: true`, `PCLOUDY_RESIGNED_FILE`.
74
+
75
+ ## `pcloudy_resign_app`
76
+
77
+ Re-signs an `.ipa` that's already in pCloudy My Apps with pCloudy's certificate, so it installs on pCloudy iPhones and iPads. If you don't pass `file:`, it uses the last `pcloudy_upload_app` upload.
78
+
79
+ ```ruby
80
+ pcloudy_upload_app
81
+ pcloudy_resign_app
82
+
83
+ pcloudy_resign_app(file: "MyApp-1794551836.ipa") # a file already in My Apps
84
+ ```
85
+
86
+ | Option | Env var | Default | Description |
87
+ |---|---|---|---|
88
+ | `file` | `PCLOUDY_RESIGN_FILE` | last upload | File name in pCloudy My Apps |
89
+ | `timeout` | `PCLOUDY_RESIGN_TIMEOUT` | `600` | Seconds to wait for re-signing to finish |
90
+ | `poll_interval` | — | `5` | Seconds between progress checks |
91
+
92
+ **Returns:** the re-signed file name, e.g. `MyApp-1794551836_Resigned1794551854668.ipa`.
93
+ **Sets:** `PCLOUDY_RESIGNED_FILE` and `PCLOUDY_APP_FILE`.
94
+
31
95
  ## `pcloudy_list_devices`
32
96
 
33
97
  ```ruby
@@ -59,5 +123,7 @@ The action returns an array of device hashes (`id`, `full_name`, `display_name`,
59
123
  ```bash
60
124
  bundle install
61
125
  bundle exec rspec # unit tests (HTTP is stubbed)
62
- bundle exec fastlane list_devices platform:ios # live run, needs fastlane/.env
126
+ bundle exec fastlane list_devices platform:ios # live runs, need fastlane/.env
127
+ bundle exec fastlane upload file:/path/App.ipa resign:true
128
+ bundle exec fastlane resign file:App-1794551836.ipa
63
129
  ```
@@ -1,6 +1,6 @@
1
1
  require "fastlane/action"
2
2
  require "terminal-table"
3
- require_relative "../helper/pcloudy_client"
3
+ require_relative "../helper/pcloudy_options"
4
4
 
5
5
  module Fastlane
6
6
  module Actions
@@ -12,9 +12,7 @@ module Fastlane
12
12
  PLATFORMS = %w(ios android all).freeze
13
13
 
14
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])
15
+ client = Helper::PcloudyOptions.client(params)
18
16
 
19
17
  platforms = params[:platform] == "all" ? %w(ios android) : [params[:platform]]
20
18
  devices = platforms.flat_map do |platform|
@@ -104,24 +102,7 @@ module Fastlane
104
102
  end
105
103
 
106
104
  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),
105
+ Helper::PcloudyOptions.auth + [
125
106
  FastlaneCore::ConfigItem.new(key: :platform,
126
107
  env_name: "PCLOUDY_PLATFORM",
127
108
  description: "Device platform: ios, android or all",
@@ -0,0 +1,86 @@
1
+ require "fastlane/action"
2
+ require_relative "../helper/pcloudy_options"
3
+ require_relative "pcloudy_upload_app_action"
4
+
5
+ module Fastlane
6
+ module Actions
7
+ class PcloudyResignAppAction < Action
8
+ def self.run(params)
9
+ filename = params[:file] || Actions.lane_context[SharedValues::PCLOUDY_UPLOADED_FILE]
10
+ UI.user_error!("No app to re-sign. Run pcloudy_upload_app first or pass file: (a file name in pCloudy My Apps)") if filename.to_s.empty?
11
+
12
+ client = Helper::PcloudyOptions.client(params)
13
+ signed = Helper::PcloudyOptions.resign(client, filename, timeout: params[:timeout], interval: params[:poll_interval])
14
+
15
+ Actions.lane_context[SharedValues::PCLOUDY_RESIGNED_FILE] = signed
16
+ Actions.lane_context[SharedValues::PCLOUDY_APP_FILE] = signed
17
+ signed
18
+ rescue Helper::PcloudyClient::Error => e
19
+ UI.user_error!(e.message)
20
+ end
21
+
22
+ #####################################################
23
+ # @!group Documentation
24
+ #####################################################
25
+
26
+ def self.description
27
+ "Re-signs an uploaded .ipa with pCloudy's certificate so it installs on pCloudy iOS devices"
28
+ end
29
+
30
+ def self.details
31
+ "Starts re-signing on pCloudy, waits for it to reach 100% and returns the re-signed file name in My Apps. " \
32
+ "Uses the file from the last pcloudy_upload_app unless you pass file:."
33
+ end
34
+
35
+ def self.authors
36
+ ["bikee1235"]
37
+ end
38
+
39
+ def self.return_value
40
+ "The re-signed file name in pCloudy My Apps, e.g. MyApp-1794551836_Resigned1794551854668.ipa"
41
+ end
42
+
43
+ def self.output
44
+ [
45
+ ["PCLOUDY_RESIGNED_FILE", "Re-signed file name in pCloudy My Apps"],
46
+ ["PCLOUDY_APP_FILE", "Set to the re-signed file, for installing on devices"]
47
+ ]
48
+ end
49
+
50
+ def self.available_options
51
+ Helper::PcloudyOptions.auth + [
52
+ FastlaneCore::ConfigItem.new(key: :file,
53
+ env_name: "PCLOUDY_RESIGN_FILE",
54
+ description: "File name in pCloudy My Apps. Defaults to the last pcloudy_upload_app upload",
55
+ optional: true,
56
+ type: String),
57
+ FastlaneCore::ConfigItem.new(key: :timeout,
58
+ env_name: "PCLOUDY_RESIGN_TIMEOUT",
59
+ description: "Seconds to wait for re-signing to finish",
60
+ default_value: 600,
61
+ type: Integer),
62
+ FastlaneCore::ConfigItem.new(key: :poll_interval,
63
+ description: "Seconds between progress checks",
64
+ default_value: 5,
65
+ type: Integer)
66
+ ]
67
+ end
68
+
69
+ def self.example_code
70
+ [
71
+ 'pcloudy_upload_app
72
+ pcloudy_resign_app',
73
+ 'pcloudy_resign_app(file: "MyApp-1794551836.ipa")'
74
+ ]
75
+ end
76
+
77
+ def self.category
78
+ :testing
79
+ end
80
+
81
+ def self.is_supported?(platform)
82
+ platform == :ios
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,144 @@
1
+ require "fastlane/action"
2
+ require_relative "../helper/pcloudy_options"
3
+
4
+ module Fastlane
5
+ module Actions
6
+ module SharedValues
7
+ PCLOUDY_UPLOADED_FILE = :PCLOUDY_UPLOADED_FILE
8
+ PCLOUDY_RESIGNED_FILE = :PCLOUDY_RESIGNED_FILE
9
+ PCLOUDY_APP_FILE = :PCLOUDY_APP_FILE
10
+ end
11
+
12
+ class PcloudyUploadAppAction < Action
13
+ EXTENSIONS = %w(.ipa .apk).freeze
14
+
15
+ def self.run(params)
16
+ path = params[:file] || default_file
17
+ UI.user_error!("No app to upload. Pass file: or run build_app (iOS) / gradle (Android) first") if path.to_s.empty?
18
+
19
+ path = File.expand_path(path)
20
+ UI.user_error!("File not found: #{path}") unless File.file?(path)
21
+ ext = File.extname(path).downcase
22
+ UI.user_error!("Unsupported file type '#{ext}'. pCloudy apps must be #{EXTENSIONS.join(' or ')}") unless EXTENSIONS.include?(ext)
23
+ UI.user_error!("resign: true only works for iOS apps (.ipa); #{File.basename(path)} is #{ext}") if params[:resign] && ext != ".ipa"
24
+
25
+ client = Helper::PcloudyOptions.client(params)
26
+
27
+ UI.message("Uploading #{File.basename(path)} (#{format_size(File.size(path))}) to pCloudy...")
28
+ name = client.upload(path, timeout: params[:upload_timeout])
29
+ UI.success("Uploaded as #{name}")
30
+
31
+ if params[:wait_for_processing]
32
+ UI.message("Waiting for pCloudy to register #{name}...")
33
+ client.wait_for_file(name, timeout: params[:processing_timeout])
34
+ end
35
+
36
+ Actions.lane_context[SharedValues::PCLOUDY_UPLOADED_FILE] = name
37
+ Actions.lane_context[SharedValues::PCLOUDY_APP_FILE] = name
38
+
39
+ if params[:resign]
40
+ name = Helper::PcloudyOptions.resign(client, name, timeout: params[:resign_timeout], interval: 5)
41
+ Actions.lane_context[SharedValues::PCLOUDY_RESIGNED_FILE] = name
42
+ Actions.lane_context[SharedValues::PCLOUDY_APP_FILE] = name
43
+ end
44
+
45
+ name
46
+ rescue Helper::PcloudyClient::Error => e
47
+ UI.user_error!(e.message)
48
+ end
49
+
50
+ # The .ipa from build_app/gym, or else the .apk from gradle.
51
+ def self.default_file
52
+ context = Actions.lane_context
53
+ [context[:IPA_OUTPUT_PATH], context[:GRADLE_APK_OUTPUT_PATH]].find { |value| !value.to_s.empty? }
54
+ end
55
+
56
+ def self.format_size(bytes)
57
+ bytes >= 1024 * 1024 ? format("%.1f MB", bytes / 1024.0 / 1024) : format("%.0f KB", bytes / 1024.0)
58
+ end
59
+
60
+ #####################################################
61
+ # @!group Documentation
62
+ #####################################################
63
+
64
+ def self.description
65
+ "Uploads an .ipa or .apk to pCloudy (My Apps), optionally re-signing the .ipa"
66
+ end
67
+
68
+ def self.details
69
+ "Uploads the app built by build_app or gradle (or any file you pass) to pCloudy, waits until pCloudy has " \
70
+ "registered it, and returns the name pCloudy stored it under. pCloudy adds a timestamp to the name when " \
71
+ "a file with the same name already exists, so always use the returned name or PCLOUDY_UPLOADED_FILE."
72
+ end
73
+
74
+ def self.authors
75
+ ["bikee1235"]
76
+ end
77
+
78
+ def self.return_value
79
+ "The file name in pCloudy My Apps (the re-signed copy when resign: true)"
80
+ end
81
+
82
+ def self.output
83
+ [
84
+ ["PCLOUDY_UPLOADED_FILE", "File name pCloudy stored the upload under"],
85
+ ["PCLOUDY_RESIGNED_FILE", "Re-signed file name (when resign: true)"],
86
+ ["PCLOUDY_APP_FILE", "The file to install on devices: re-signed copy if any, otherwise the upload"]
87
+ ]
88
+ end
89
+
90
+ def self.available_options
91
+ Helper::PcloudyOptions.auth + [
92
+ FastlaneCore::ConfigItem.new(key: :file,
93
+ env_name: "PCLOUDY_APP_PATH",
94
+ description: "Path to the .ipa or .apk. Defaults to the output of build_app or gradle",
95
+ optional: true,
96
+ type: String),
97
+ FastlaneCore::ConfigItem.new(key: :resign,
98
+ env_name: "PCLOUDY_RESIGN",
99
+ description: "Re-sign the uploaded .ipa with pCloudy's certificate (iOS only)",
100
+ default_value: false,
101
+ type: Boolean),
102
+ FastlaneCore::ConfigItem.new(key: :wait_for_processing,
103
+ env_name: "PCLOUDY_WAIT_FOR_PROCESSING",
104
+ description: "Wait until the upload appears in pCloudy My Apps",
105
+ default_value: true,
106
+ type: Boolean),
107
+ FastlaneCore::ConfigItem.new(key: :upload_timeout,
108
+ env_name: "PCLOUDY_UPLOAD_TIMEOUT",
109
+ description: "Seconds to allow for the upload request",
110
+ default_value: 600,
111
+ type: Integer),
112
+ FastlaneCore::ConfigItem.new(key: :processing_timeout,
113
+ env_name: "PCLOUDY_PROCESSING_TIMEOUT",
114
+ description: "Seconds to wait for the upload to appear in My Apps",
115
+ default_value: 120,
116
+ type: Integer),
117
+ FastlaneCore::ConfigItem.new(key: :resign_timeout,
118
+ env_name: "PCLOUDY_RESIGN_TIMEOUT",
119
+ description: "Seconds to wait for re-signing to finish (when resign: true)",
120
+ default_value: 600,
121
+ type: Integer)
122
+ ]
123
+ end
124
+
125
+ def self.example_code
126
+ [
127
+ 'build_app(scheme: "MyApp", export_method: "development")
128
+ pcloudy_upload_app(resign: true)',
129
+ 'gradle(task: "assembleDebug")
130
+ pcloudy_upload_app',
131
+ 'name = pcloudy_upload_app(file: "./build/MyApp.ipa")'
132
+ ]
133
+ end
134
+
135
+ def self.category
136
+ :testing
137
+ end
138
+
139
+ def self.is_supported?(platform)
140
+ [:ios, :android].include?(platform)
141
+ end
142
+ end
143
+ end
144
+ end
@@ -39,19 +39,93 @@ module Fastlane
39
39
  result["models"] || []
40
40
  end
41
41
 
42
+ # POST /api/upload_file (multipart). Returns the file name pCloudy stored the app under.
43
+ # pCloudy renames the upload (adds a timestamp) when a file with the same name already exists,
44
+ # so callers must use this return value, never the local file name.
45
+ def upload(path, filter: "all", timeout: 600)
46
+ request = Net::HTTP::Post.new(endpoint("/api/upload_file"))
47
+ result = File.open(path, "rb") do |io|
48
+ request.set_form([["file", io, { filename: File.basename(path) }],
49
+ ["source_type", "raw"],
50
+ ["token", token],
51
+ ["filter", filter]],
52
+ "multipart/form-data")
53
+ perform(request, timeout: timeout)
54
+ end
55
+ name = result["file"].to_s
56
+ raise(Error, "pCloudy upload succeeded but returned no file name") if name.empty?
57
+
58
+ name
59
+ end
60
+
61
+ # POST /api/drive. Returns the files in "My Apps" as hashes ({"file" => "...", "size_KB" => ..., ...}).
62
+ def files(filter: "all")
63
+ post_json("/api/drive", token: token, limit: 0, filter: filter)["files"] || []
64
+ end
65
+
66
+ # Waits until an uploaded file shows up in "My Apps". Raises Error after timeout seconds.
67
+ def wait_for_file(name, timeout: 120, interval: 3)
68
+ deadline = monotonic_now + timeout
69
+ loop do
70
+ return true if files.any? { |f| f["file"] == name }
71
+ raise(Error, "#{name} did not appear in pCloudy My Apps within #{timeout}s") if monotonic_now >= deadline
72
+
73
+ sleep(interval)
74
+ end
75
+ end
76
+
77
+ # Re-signs an uploaded .ipa with pCloudy's certificate: resign/initiate -> resign/progress -> resign/download.
78
+ # Yields the progress percentage while waiting. Returns the re-signed file name in "My Apps".
79
+ def resign(filename, timeout: 600, interval: 5)
80
+ deadline = monotonic_now + timeout
81
+ resign_token = with_retry_until(deadline, interval, /does not exist/i) do
82
+ post_json("/api/resign/initiate", token: token, filename: filename)["resign_token"]
83
+ end
84
+ raise(Error, "pCloudy resign/initiate returned no resign_token") if resign_token.to_s.empty?
85
+
86
+ loop do
87
+ status = post_json("/api/resign/progress", token: token, resign_token: resign_token, filename: filename)["resign_status"].to_i
88
+ yield(status) if block_given?
89
+ break if status >= 100
90
+ raise(Error, "Re-signing #{filename} did not finish within #{timeout}s (last progress #{status}%)") if monotonic_now >= deadline
91
+
92
+ sleep(interval)
93
+ end
94
+
95
+ signed = post_json("/api/resign/download", token: token, resign_token: resign_token, filename: filename)["resign_file"].to_s
96
+ raise(Error, "pCloudy resign/download returned no file name") if signed.empty?
97
+
98
+ signed
99
+ end
100
+
42
101
  private
43
102
 
103
+ # Runs the block, retrying while it raises an Error whose message matches `pattern`, until `deadline`.
104
+ def with_retry_until(deadline, interval, pattern)
105
+ yield
106
+ rescue Error => e
107
+ raise if e.message !~ pattern || monotonic_now >= deadline
108
+
109
+ sleep(interval)
110
+ retry
111
+ end
112
+
113
+ def monotonic_now
114
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
115
+ end
116
+
44
117
  def post_json(path, payload)
45
118
  request = Net::HTTP::Post.new(endpoint(path), "Content-Type" => "application/json")
46
119
  request.body = JSON.generate(payload)
47
120
  perform(request)
48
121
  end
49
122
 
50
- def perform(request)
123
+ def perform(request, timeout: @timeout)
51
124
  response = Net::HTTP.start(@base.host, @base.port,
52
125
  use_ssl: @base.scheme == "https",
53
126
  open_timeout: @timeout,
54
- read_timeout: @timeout) do |http|
127
+ read_timeout: timeout,
128
+ write_timeout: timeout) do |http|
55
129
  http.request(request)
56
130
  end
57
131
 
@@ -61,9 +135,10 @@ module Fastlane
61
135
  result = JSON.parse(response.body.to_s)["result"]
62
136
  raise(Error, "pCloudy #{path} returned an unexpected response") unless result.kind_of?(Hash)
63
137
 
64
- unless result["code"].to_i == 200
138
+ # Some endpoints (e.g. upload_file) omit "code"; treat an explicit error or a non-200 code as failure.
139
+ if result["error"] || (result.key?("code") && result["code"].to_i != 200)
65
140
  message = result["error"] || result["msg"] || result["message"] || "unknown error"
66
- raise(Error, "pCloudy #{path} failed (code #{result['code']}): #{message}")
141
+ raise(Error, "pCloudy #{path} failed (code #{result['code'] || 'n/a'}): #{message}")
67
142
  end
68
143
 
69
144
  result
@@ -0,0 +1,53 @@
1
+ require "fastlane_core/configuration/config_item"
2
+ require_relative "pcloudy_client"
3
+
4
+ module Fastlane
5
+ module Helper
6
+ # Options and client setup shared by every pcloudy_* action.
7
+ module PcloudyOptions
8
+ def self.auth
9
+ [
10
+ FastlaneCore::ConfigItem.new(key: :username,
11
+ env_name: "PCLOUDY_USERNAME",
12
+ description: "pCloudy account email",
13
+ optional: false,
14
+ type: String),
15
+ FastlaneCore::ConfigItem.new(key: :api_key,
16
+ env_name: "PCLOUDY_API_KEY",
17
+ description: "pCloudy API key (Settings > API Key)",
18
+ optional: false,
19
+ sensitive: true,
20
+ code_gen_sensitive: true,
21
+ type: String),
22
+ FastlaneCore::ConfigItem.new(key: :cloud_url,
23
+ env_name: "PCLOUDY_CLOUD_URL",
24
+ description: "pCloudy cloud URL (change for private clouds)",
25
+ default_value: PcloudyClient::DEFAULT_CLOUD_URL,
26
+ type: String)
27
+ ]
28
+ end
29
+
30
+ def self.client(params)
31
+ PcloudyClient.new(username: params[:username],
32
+ api_key: params[:api_key],
33
+ cloud_url: params[:cloud_url])
34
+ end
35
+
36
+ # Re-signs `filename` in pCloudy, logging progress. Shared by pcloudy_resign_app and pcloudy_upload_app(resign: true).
37
+ def self.resign(client, filename, timeout:, interval:)
38
+ unless File.extname(filename).casecmp?(".ipa")
39
+ UI.user_error!("Only iOS apps (.ipa) can be re-signed on pCloudy; got #{filename}")
40
+ end
41
+
42
+ UI.message("Re-signing #{filename} on pCloudy...")
43
+ last = nil
44
+ signed = client.resign(filename, timeout: timeout, interval: interval) do |progress|
45
+ UI.message("Re-sign progress: #{progress}%") unless progress == last
46
+ last = progress
47
+ end
48
+ UI.success("Re-signed app in pCloudy: #{signed}")
49
+ signed
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Pcloudy
3
- VERSION = "0.1.0"
3
+ VERSION = "0.2.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-pcloudy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bikee1235
@@ -91,9 +91,9 @@ dependencies:
91
91
  - - "~>"
92
92
  - !ruby/object:Gem::Version
93
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.
94
+ description: 'Use pCloudy (device.pcloudy.com or a private cloud) from your fastlane
95
+ lanes: list available real iOS and Android devices, upload the .ipa or .apk you
96
+ just built, and re-sign iOS apps with pCloudy''s certificate.'
97
97
  email:
98
98
  - bikee.bihari@sstsinc.com
99
99
  executables: []
@@ -105,7 +105,10 @@ files:
105
105
  - README.md
106
106
  - lib/fastlane/plugin/pcloudy.rb
107
107
  - lib/fastlane/plugin/pcloudy/actions/pcloudy_list_devices_action.rb
108
+ - lib/fastlane/plugin/pcloudy/actions/pcloudy_resign_app_action.rb
109
+ - lib/fastlane/plugin/pcloudy/actions/pcloudy_upload_app_action.rb
108
110
  - lib/fastlane/plugin/pcloudy/helper/pcloudy_client.rb
111
+ - lib/fastlane/plugin/pcloudy/helper/pcloudy_options.rb
109
112
  - lib/fastlane/plugin/pcloudy/version.rb
110
113
  homepage: https://github.com/Smart-Software-Testing-Solutions-Opkey/fastlane-plugin-pcloudy
111
114
  licenses: