fastlane-plugin-testappio 1.0.4 → 2.0.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: d55de14e5648c1807325ebdea9de2ebf7553a9c61d8a8c7e4f58484409c29dc1
4
- data.tar.gz: 7d560dce67bef063702fb942b3bbfa508a3a55cf49a98e8b3a7d5e65bb803c1f
3
+ metadata.gz: 7686bbb53e5fb7ae977b5830d28a57b9638909a2e641115d0a6b316fc7645f74
4
+ data.tar.gz: 7c612f1238a999bec957e82116037a32e5949711ef68db80d9d8d3d8d2b18c9d
5
5
  SHA512:
6
- metadata.gz: 57d59c0bd2d320dcec29a97b53a21de3e2cc8290967738dd4591cabfd2317beb03567d8d92823f1bf6b39e10e7e8c4b00a2b23f07ad40408723178ae4dd0f637
7
- data.tar.gz: 310cc7eb9e585a5b6dbb2eadb28084b921a8a217a50723abd7c365be3f1d02abee51e7427191a5412e7fdfae19f32b61fbeede3aa090359d153868e08b256f03
6
+ metadata.gz: 6a2c5b8b0ab71fd8ac4e7e9ee0df8c30c067f161764522ad615a8d1860ccae527f7c7c512e7493261f8017842fc624001ea911ca393ac1b94000b7180df03b9d
7
+ data.tar.gz: aa74c763e07c79000723caf43bf6fbb4c3b93cbfae4bf7f7f7cee5cf2126b43333c7b55857527fc85fc999f77b4488bb01e86675851686a5b5865d74d00382b1
data/CHANGELOG.md ADDED
@@ -0,0 +1,66 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [2.0.0] - 2026-05-28
11
+
12
+ ### Breaking
13
+ - **Ruby ≥ 3.0 required.** Customers on Ruby 2.6 or 2.7 should pin to `~> 1.0` in their `Pluginfile`.
14
+
15
+ ### Fixed
16
+ - `fastlane action upload_to_testappio` (the help/docs introspection command) no longer crashes with `NameError`. A `nill` typo in `return_value` was breaking this introspection path — actual uploads were unaffected, but Fastlane's plugin docs couldn't render.
17
+ - `validate_file_path` no longer raises `TypeError` when `apk_file` or `ipa_file` is `nil` — affected iOS-only and Android-only lanes that didn't set the other file param.
18
+ - `handle_error` no longer fails the upload when `ta-cli` writes warning or info lines to stderr — only real `Error:` lines fail the lane now.
19
+ - `--api_token` is now masked (`********`) in verbose-mode command logging — previously the token was echoed in plain text via `UI.command` in `fastlane --verbose` runs.
20
+
21
+ ### Added
22
+ - `spec.description` and `spec.metadata` (rubygems_mfa_required, source_code_uri, changelog_uri, bug_tracker_uri) in gemspec.
23
+ - Test coverage raised from 36.79% → **100% line / 86.76% branch** (49 unit + 2 opt-in `:integration` specs exercise the real `ta-cli` binary).
24
+ - Concurrent stdout/stderr reading in `call_ta_cli` via threads — prevents potential deadlock when ta-cli emits a large stderr buffer before stdout completes. Threads are cleanly killed in an `ensure` block if the surrounding popen3 block raises.
25
+ - `Open3.popen3` now uses the array form (no shell interpretation), so shell metacharacters in `api_token` / `release_notes` / file paths are passed literally to ta-cli without depending on manual escaping.
26
+ - `handle_error` now includes the actual `/Error/`-matching stderr lines in the raised `FastlaneError` message so users see the real failure reason in their CI logs.
27
+ - Shared spec helpers in `spec/spec_helper.rb` for stubbing `Open3.popen3` and `FastlaneCore::Globals.verbose?`.
28
+ - SimpleCov `minimum_coverage line: 95, branch: 90` gate (CI-only — local single-file runs no longer trip the gate).
29
+ - Opt-in `:integration` spec suite in `spec/integration/` — runs the real `ta-cli` binary against `fastlane/sample/*` with fake credentials to verify the deploy flow end-to-end. Run with `bundle exec rspec --tag integration` or `INTEGRATION=1 bundle exec rspec`.
30
+ - GitHub Actions CI: matrix over Ruby 3.0–3.3 × ubuntu/macos, with separate lint, test, and build jobs.
31
+ - Codecov coverage upload (via simplecov-cobertura) from one matrix cell.
32
+ - Tag-triggered `release.yml` workflow that builds and pushes the gem to RubyGems.
33
+ - Dependabot config for bundler and github-actions ecosystems.
34
+ - README badges: CI status, Codecov coverage, downloads, license, Ruby version, plus a Compatibility section.
35
+ - `self_update: true` example in `fastlane/Fastfile`.
36
+
37
+ ### Changed
38
+ - `rubocop` bumped from `1.12.1` to `~> 1.50` (dependent gems aligned).
39
+ - `simplecov`, `pry`, `fastlane`, `rspec_junit_formatter` bumped to current majors.
40
+ - `.rubocop.yml` `TargetRubyVersion` raised to `3.0`; deprecated cop names updated.
41
+
42
+ ### Removed
43
+ - `.travis.yml` and `.circleci/config.yml` (both obsolete; replaced by GitHub Actions).
44
+ - `spec.test_files` from gemspec (deprecated by RubyGems).
45
+ - `rubocop-require_tools` dev dependency (covered by rubocop 1.50+).
46
+
47
+ ## [1.0.5] - 2023-04-27
48
+
49
+ ### Changed
50
+ - Update Fastlane plugin link in README.
51
+
52
+ ## [1.0.4] - 2023
53
+
54
+ ### Changed
55
+ - Internal param refinement and result printing.
56
+
57
+ ## [1.0.3] - 2022
58
+
59
+ ### Changed
60
+ - Rolled back to v2 tag of ta-cli.
61
+
62
+ [Unreleased]: https://github.com/testappio/fastlane-plugin-testappio/compare/v2.0.0...HEAD
63
+ [2.0.0]: https://github.com/testappio/fastlane-plugin-testappio/compare/v1.0.5...v2.0.0
64
+ [1.0.5]: https://github.com/testappio/fastlane-plugin-testappio/compare/v1.0.4...v1.0.5
65
+ [1.0.4]: https://github.com/testappio/fastlane-plugin-testappio/compare/v1.0.3...v1.0.4
66
+ [1.0.3]: https://github.com/testappio/fastlane-plugin-testappio/releases/tag/v1.0.3
data/README.md CHANGED
@@ -1,107 +1,151 @@
1
1
  # [<img src="https://assets.testapp.io/logo/blue.svg" alt="TestApp.io"/>](https://testapp.io/) Fastlane Plugin
2
2
 
3
- > BETA mode. Your feedback is highly appreciated.
3
+ Upload your Android (`.apk`) and iOS (`.ipa`) builds to [TestApp.io](https://testapp.io) straight from your Fastlane lane. One action, both platforms, release notes from git, optional team notifications.
4
4
 
5
- A Fastlane plugin to upload both Android & iOS apps to TestApp.io to notify everyone for testing and feedback.
5
+ [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-testappio)
6
+ [![Gem Version](https://img.shields.io/gem/v/fastlane-plugin-testappio)](https://rubygems.org/gems/fastlane-plugin-testappio)
7
+ [![CI](https://github.com/testappio/fastlane-plugin-testappio/actions/workflows/ci.yml/badge.svg)](https://github.com/testappio/fastlane-plugin-testappio/actions/workflows/ci.yml)
8
+ [![codecov](https://codecov.io/gh/testappio/fastlane-plugin-testappio/branch/main/graph/badge.svg)](https://codecov.io/gh/testappio/fastlane-plugin-testappio)
9
+ [![Downloads](https://img.shields.io/gem/dt/fastlane-plugin-testappio)](https://rubygems.org/gems/fastlane-plugin-testappio)
10
+ [![License](https://img.shields.io/github/license/testappio/fastlane-plugin-testappio)](LICENSE)
11
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.0-CC342D.svg)](https://www.ruby-lang.org/)
6
12
 
7
- [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-testappio) [![Gem Version](https://badge.fury.io/rb/fastlane-plugin-testappio.svg)](https://badge.fury.io/rb/fastlane-plugin-testappio)
8
-
9
- ## Getting started
13
+ ---
10
14
 
11
- This project is a [_Fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-testappio`, add it to your project by running:
15
+ ## Install
12
16
 
13
17
  ```bash
14
18
  fastlane add_plugin testappio
15
19
  ```
16
20
 
17
- ## Configuration
21
+ That's it. The plugin will install the underlying [`ta-cli`](https://help.testapp.io/ta-cli/) binary the first time you upload.
18
22
 
19
- _More info here: [https://help.testapp.io/ta-cli](https://help.testapp.io/ta-cli/)_
23
+ ## Quick start
20
24
 
21
- | Key | Description | Env Var(s) | Default |
22
- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | ------- |
23
- | api_token | You can get it from https://portal.testapp.io/settings/api-credentials | TESTAPPIO_API_TOKEN | |
24
- | app_id | You can get it from your app page at [https://portal.testapp.io/apps](https://portal.testapp.io/apps?action=select-for-integrations) | TESTAPPIO_APP_ID | |
25
- | release | It can be either both or Android or iOS | TESTAPPIO_RELEASE | |
26
- | apk_file | Path to the Android APK file | TESTAPPIO_ANDROID_PATH | |
27
- | ipa_file | Path to the iOS IPA file | TESTAPPIO_IOS_PATH | |
28
- | release_notes | Manually add the release notes to be displayed for the testers | TESTAPPIO_RELEASE_NOTES | |
29
- | git_release_notes | Collect release notes from the latest git commit message to be displayed for the testers: true or false | TESTAPPIO_GIT_RELEASE_NOTES | true |
30
- | git_commit_id | Include the last commit ID in the release notes (works with both release notes options): true or false | TESTAPPIO_GIT_COMMIT_ID | false |
31
- | notify | Send notifications to your team members about this release: true or false | TESTAPPIO_NOTIFY | false |
25
+ ```ruby
26
+ lane :beta do
27
+ gym(export_method: "ad-hoc") # or gradle(task: "assembleRelease")
28
+
29
+ upload_to_testappio(
30
+ api_token: ENV["TESTAPPIO_API_TOKEN"],
31
+ app_id: ENV["TESTAPPIO_APP_ID"],
32
+ notify: true
33
+ )
34
+ end
35
+ ```
32
36
 
33
- ## TestApp.io actions
37
+ Get your `api_token` at <https://portal.testapp.io/profile/tokens>.
38
+ Get your `app_id` at <https://portal.testapp.io/apps>.
34
39
 
35
- Actions provided by the CLI: [ta-cli](https://help.testapp.io/ta-cli/)
40
+ ## Configuration
36
41
 
37
- Check out the [example Fastfile](https://github.com/testappio/fastlane-plugin-testappio/blob/main/fastlane/Fastfile) to see how to use this plugin. Try it by cloning the repo, running `fastlane install_plugins` and `bundle exec fastlane test`.
42
+ | Key | Description | Env var | Default |
43
+ |---|---|---|---|
44
+ | `api_token` | API token from <https://portal.testapp.io/profile/tokens> | `TESTAPPIO_API_TOKEN` | — |
45
+ | `app_id` | App ID from your <https://portal.testapp.io/apps> page | `TESTAPPIO_APP_ID` | — |
46
+ | `release` | `ios`, `android`, or `both` | `TESTAPPIO_RELEASE` | current platform |
47
+ | `apk_file` | Path to the Android `.apk` | `TESTAPPIO_ANDROID_PATH` | gradle's output |
48
+ | `ipa_file` | Path to the iOS `.ipa` | `TESTAPPIO_IOS_PATH` | gym's output |
49
+ | `release_notes` | Manual release notes | `TESTAPPIO_RELEASE_NOTES` | — |
50
+ | `git_release_notes` | Use the latest git commit message as release notes | `TESTAPPIO_GIT_RELEASE_NOTES` | `true` |
51
+ | `git_commit_id` | Append the latest commit SHA to the release notes | `TESTAPPIO_GIT_COMMIT_ID` | `false` |
52
+ | `notify` | Notify team members about the new release | `TESTAPPIO_NOTIFY` | `false` |
53
+ | `self_update` | Auto-update `ta-cli` when a new version is available | `TESTAPPIO_SELF_UPDATE` | `true` |
38
54
 
39
- To upload after the Fastlane `gym` action:
55
+ ## Examples
40
56
 
41
- ##### iOS
57
+ ### iOS only
42
58
 
43
59
  ```ruby
44
- lane :beta do
45
-
46
- increment_build_number
47
- match(type: "adhoc")
48
- gym(export_method: "ad-hoc")
49
-
50
- upload_to_testappio(
51
- api_token: "Your API Token",
52
- app_id: "Your App ID",
53
- release_notes: "My release notes here...",
54
- git_release_notes: true,
55
- git_commit_id: false,
56
- notify: true
57
- )
60
+ lane :beta_ios do
61
+ match(type: "adhoc")
62
+ gym(export_method: "ad-hoc")
63
+
64
+ upload_to_testappio(
65
+ api_token: ENV["TESTAPPIO_API_TOKEN"],
66
+ app_id: ENV["TESTAPPIO_APP_ID"],
67
+ release: "ios",
68
+ release_notes: "Bug fixes and improvements",
69
+ notify: true
70
+ )
71
+ end
72
+ ```
58
73
 
59
- clean_build_artifacts #optional
74
+ ### Android only
60
75
 
61
- end
76
+ ```ruby
77
+ lane :beta_android do
78
+ gradle(task: "clean assembleRelease")
79
+
80
+ upload_to_testappio(
81
+ api_token: ENV["TESTAPPIO_API_TOKEN"],
82
+ app_id: ENV["TESTAPPIO_APP_ID"],
83
+ release: "android",
84
+ git_release_notes: true,
85
+ git_commit_id: true,
86
+ notify: true
87
+ )
88
+ end
62
89
  ```
63
90
 
64
- ##### Android
91
+ ### Both platforms in one lane
65
92
 
66
93
  ```ruby
67
- lane :beta do
94
+ lane :beta_both do
95
+ gradle(task: "clean assembleRelease")
96
+ gym(export_method: "ad-hoc")
97
+
98
+ upload_to_testappio(
99
+ api_token: ENV["TESTAPPIO_API_TOKEN"],
100
+ app_id: ENV["TESTAPPIO_APP_ID"],
101
+ release: "both",
102
+ notify: true
103
+ )
104
+ end
105
+ ```
68
106
 
69
- increment_version_code #[Optional] fastlane add_plugin increment_version_code
107
+ A runnable example lives in [`fastlane/Fastfile`](fastlane/Fastfile). Try it with `bundle exec fastlane test`.
70
108
 
71
- gradle(task: "clean assembleDebug") #or clean assembleRelease
109
+ ## Compatibility
72
110
 
73
- upload_to_testappio(
74
- api_token: "Your API Token", #You can get it from https://portal.testapp.io/settings/api-credentials
75
- app_id: "Your App ID", #You can get it from your app page in https://portal.testapp.io/apps
76
- release_notes: "My release notes here...",
77
- git_release_notes: true,
78
- git_commit_id: false,
79
- notify: true
80
- )
111
+ | Plugin version | Ruby | Status |
112
+ |---|---|---|
113
+ | **2.x** | `>= 3.0` | Active feature work + security fixes |
114
+ | **1.x** | `>= 2.6` | Maintenance only — security fixes through May 2027 |
81
115
 
82
- end
83
- ```
116
+ If your CI image uses Ruby 2.6 or 2.7, pin to `~> 1.0` in your `Pluginfile`:
84
117
 
85
- ## Troubleshooting
86
-
87
- If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide.
118
+ ```ruby
119
+ gem "fastlane-plugin-testappio", "~> 1.0"
120
+ ```
88
121
 
89
- ## Using _Fastlane_ plugins
122
+ When you upgrade your CI to Ruby 3.0+, drop the pin and you'll get 2.x automatically.
90
123
 
91
- Check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/) for more information about how the plugin system works.
124
+ ## Troubleshooting
92
125
 
93
- ## About _Fastlane_
126
+ - **`fastlane add_plugin testappio` fails on Bundler 2.0 issues** — make sure your Ruby is 3.0 or higher (`ruby -v`).
127
+ - **Upload fails with a `ta-cli` error** — the error message now includes the underlying `ta-cli` output so you can see exactly what went wrong (auth, network, provisioning, etc.). Run with `fastlane --verbose` for full context.
128
+ - **Token visible in CI logs** — should never happen in 2.x; tokens are masked as `********`. If you see this in 1.x, upgrade.
129
+ - **Other plugin issues** — see the official [Fastlane plugin troubleshooting guide](https://docs.fastlane.tools/plugins/plugins-troubleshooting/).
94
130
 
95
- _Fastlane_ is the easiest way to automate beta deployments and releases for iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools).
131
+ ## Contributing
96
132
 
97
- ---
133
+ PRs welcome. Local development:
98
134
 
99
- ## Feedback & Support
135
+ ```bash
136
+ bundle install # install dev dependencies
137
+ bundle exec rspec # run the unit test suite (49 specs)
138
+ bundle exec rubocop # lint
139
+ bundle exec rake # spec + rubocop together
140
+ INTEGRATION=1 bundle exec rspec # also run the 2 :integration specs (needs ta-cli installed)
141
+ ```
100
142
 
101
- Developers built [TestApp.io](https://testapp.io) to solve the pain of app distribution & feedback for mobile app development teams.
143
+ Every PR adds an entry under `## [Unreleased]` in [`CHANGELOG.md`](CHANGELOG.md). See it for the full list of what changed between versions.
102
144
 
103
- Join our [community](https://help.testapp.io/faq/join-our-community/) for feedback and support.
145
+ ## Feedback & support
104
146
 
105
- Check out our [Help Center](https://help.testapp.io/) for more info and other integrations.
147
+ - **Help center:** <https://help.testapp.io/>
148
+ - **Community:** <https://help.testapp.io/faq/join-our-community/>
149
+ - **Bug reports / feature requests:** [GitHub issues](https://github.com/testappio/fastlane-plugin-testappio/issues)
106
150
 
107
- Happy releasing 🎉
151
+ Built by [TestApp.io](https://testapp.io) — happy releasing 🚀
@@ -4,7 +4,6 @@ module Fastlane
4
4
  SUPPORTED_FILE_EXTENSIONS = ["apk", "ipa"]
5
5
 
6
6
  def self.run(params)
7
-
8
7
  # Check if `ta_cli` exists, install it if not
9
8
  unless Helper::TestappioHelper.check_ta_cli
10
9
  UI.error("Error detecting ta-cli")
@@ -43,7 +42,9 @@ module Fastlane
43
42
  "--git_release_notes=#{git_release_notes}",
44
43
  "--git_commit_id=#{git_commit_id}",
45
44
  "--notify=#{notify}",
46
- "--source=Fastlane"]
45
+ "--self_update=#{self_update}",
46
+ "--source=Fastlane",
47
+ "--source_version=#{Fastlane::Testappio::VERSION}"]
47
48
 
48
49
  UI.message("Uploading to TestApp.io")
49
50
  Helper::TestappioHelper.call_ta_cli(command)
@@ -51,6 +52,8 @@ module Fastlane
51
52
  end
52
53
 
53
54
  def self.validate_file_path(file_path)
55
+ return if file_path.nil?
56
+
54
57
  UI.user_error!("No file found at '#{file_path}'.") unless File.exist?(file_path)
55
58
 
56
59
  file_ext = File.extname(file_path).delete('.')
@@ -85,60 +88,60 @@ module Fastlane
85
88
  [
86
89
  FastlaneCore::ConfigItem.new(key: :api_token,
87
90
  env_name: "TESTAPPIO_API_TOKEN",
88
- description: "You can get it from https://portal.testapp.io/settings/api-credentials",
91
+ description: "You can get it from https://portal.testapp.io/profile/tokens",
89
92
  verify_block: proc do |value|
90
- UI.user_error!("No API token provided. You can get it from https://portal.testapp.io/settings/api-credentials") unless value && !value.empty?
93
+ UI.user_error!("No API token provided. You can get it from https://portal.testapp.io/profile/tokens") unless value && !value.empty?
91
94
  end),
92
95
  FastlaneCore::ConfigItem.new(key: :app_id,
93
96
  env_name: "TESTAPPIO_APP_ID",
94
- description: "You can get it from your app page in https://portal.testapp.io/apps?action=select-for-integrations",
97
+ description: "You can get it from your app page in https://portal.testapp.io/apps?to=app-integrations&tab=releases",
95
98
  is_string: false),
96
99
  FastlaneCore::ConfigItem.new(key: :release,
97
100
  env_name: "TESTAPPIO_RELEASE",
98
101
  description: "It can be either both or android or ios",
99
102
  is_string: true,
100
- default_value: Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]),
103
+ default_value: Actions.lane_context[Actions::SharedValues::PLATFORM_NAME]),
101
104
  FastlaneCore::ConfigItem.new(key: :apk_file,
102
- env_name: "TESTAPPIO_ANDROID_PATH",
103
- description: "Full path to the Android .apk file",
104
- optional: true,
105
- is_string: true,
106
- default_value: default_file_path),
105
+ env_name: "TESTAPPIO_ANDROID_PATH",
106
+ description: "Full path to the Android .apk file",
107
+ optional: true,
108
+ is_string: true,
109
+ default_value: default_file_path),
107
110
  FastlaneCore::ConfigItem.new(key: :ipa_file,
108
- env_name: "TESTAPPIO_IOS_PATH",
109
- description: "Full path to the iOS .ipa file",
110
- optional: true,
111
- is_string: true,
112
- default_value: default_file_path),
111
+ env_name: "TESTAPPIO_IOS_PATH",
112
+ description: "Full path to the iOS .ipa file",
113
+ optional: true,
114
+ is_string: true,
115
+ default_value: default_file_path),
113
116
  FastlaneCore::ConfigItem.new(key: :release_notes,
114
- env_name: "TESTAPPIO_RELEASE_NOTES",
115
- description: "Manually add the release notes to be displayed for the testers",
116
- optional: true,
117
- is_string: true),
117
+ env_name: "TESTAPPIO_RELEASE_NOTES",
118
+ description: "Manually add the release notes to be displayed for the testers",
119
+ optional: true,
120
+ is_string: true),
118
121
  FastlaneCore::ConfigItem.new(key: :git_release_notes,
119
- env_name: "TESTAPPIO_GIT_RELEASE_NOTES",
120
- description: "Collect release notes from the latest git commit message to be displayed for the testers: true or false",
121
- optional: true,
122
- is_string: false,
123
- default_value: true),
122
+ env_name: "TESTAPPIO_GIT_RELEASE_NOTES",
123
+ description: "Collect release notes from the latest git commit message to be displayed for the testers: true or false",
124
+ optional: true,
125
+ is_string: false,
126
+ default_value: true),
124
127
  FastlaneCore::ConfigItem.new(key: :git_commit_id,
125
- env_name: "TESTAPPIO_GIT_COMMIT_ID",
126
- description: "Include the last commit ID in the release notes (works with both release notes option): true or false",
127
- optional: true,
128
- is_string: false,
129
- default_value: false),
128
+ env_name: "TESTAPPIO_GIT_COMMIT_ID",
129
+ description: "Include the last commit ID in the release notes (works with both release notes option): true or false",
130
+ optional: true,
131
+ is_string: false,
132
+ default_value: false),
130
133
  FastlaneCore::ConfigItem.new(key: :notify,
131
- env_name: "TESTAPPIO_NOTIFY",
132
- description: "Send notificaitons to your team members about this release: true or false",
133
- optional: true,
134
- is_string: false,
135
- default_value: false),
134
+ env_name: "TESTAPPIO_NOTIFY",
135
+ description: "Send notificaitons to your team members about this release: true or false",
136
+ optional: true,
137
+ is_string: false,
138
+ default_value: false),
136
139
  FastlaneCore::ConfigItem.new(key: :self_update,
137
- env_name: "TESTAPPIO_SELF_UPDATE",
138
- description: "Automatically update ta-cli if a new version is available or required: true or false",
139
- optional: true,
140
- is_string: false,
141
- default_value: true)
140
+ env_name: "TESTAPPIO_SELF_UPDATE",
141
+ description: "Automatically update ta-cli if a new version is available or required: true or false",
142
+ optional: true,
143
+ is_string: false,
144
+ default_value: true)
142
145
  ]
143
146
  end
144
147
 
@@ -147,7 +150,7 @@ module Fastlane
147
150
  end
148
151
 
149
152
  def self.return_value
150
- nill
153
+ nil
151
154
  end
152
155
 
153
156
  def self.authors
@@ -1,11 +1,11 @@
1
1
  require 'fastlane_core/ui/ui'
2
+ require 'open3'
2
3
 
3
4
  module Fastlane
4
5
  UI = FastlaneCore::UI unless Fastlane.const_defined?(:UI)
5
6
 
6
7
  module Helper
7
8
  class TestappioHelper
8
-
9
9
  # Check if `ta_cli` exists, install it if not
10
10
  def self.check_ta_cli
11
11
  unless system('which ta-cli > /dev/null 2>&1')
@@ -22,8 +22,6 @@ module Fastlane
22
22
 
23
23
  # Check if there is an update available for `ta_cli`, and install it if necessary
24
24
  def self.check_update(self_update)
25
- require 'open3'
26
-
27
25
  version_output = `ta-cli version`
28
26
  UI.verbose(version_output)
29
27
 
@@ -31,14 +29,14 @@ module Fastlane
31
29
  if self_update
32
30
  UI.message("Updating ta-cli due to breaking changes...")
33
31
  else
32
+ # UI.user_error! raises FastlaneError; method never returns past this point.
34
33
  UI.user_error!("Update necessary due to breaking changes. Please update ta-cli manually or set self_update to true")
35
- return false
36
34
  end
37
35
  elsif version_output.include?("New version available")
38
36
  if self_update
39
37
  UI.message("Updating ta-cli...")
40
38
  else
41
- UI.error("New version available, but skipping update as self_update is set to false.")
39
+ UI.important("New version available, but skipping update as self_update is set to false.")
42
40
  return true
43
41
  end
44
42
  else
@@ -47,7 +45,7 @@ module Fastlane
47
45
  end
48
46
 
49
47
  update_command = "curl -Ls https://github.com/testappio/cli/releases/latest/download/install | bash"
50
- update_output, update_errors, update_status = Open3.capture3(update_command)
48
+ update_output, = Open3.capture3(update_command)
51
49
  UI.verbose(update_output)
52
50
 
53
51
  if update_output.include?("ta-cli successfully installed") || update_output.include?("ta-cli has been updated")
@@ -56,63 +54,93 @@ module Fastlane
56
54
 
57
55
  if version_output.include?("Update necessary due to breaking changes") || version_output.include?("New version available")
58
56
  UI.user_error!("Error updating ta-cli: the version is still outdated")
59
- return false
60
57
  else
61
58
  UI.success("ta-cli has been updated successfully")
62
59
  return true
63
60
  end
64
61
  else
65
62
  UI.user_error!("Error updating ta-cli: #{update_output}")
66
- return false
67
63
  end
68
64
  end
69
65
 
70
- # Handle errors from `ta_cli`, if contains 'Error', the process stops,
71
- # otherwise, just print to user console
66
+ # Handle errors from ta-cli. Raises user_error only when stderr contains an
67
+ # /Error/-matching line. Non-error stderr (warnings, info) is logged via UI.verbose.
68
+ # The raised message includes the actual error lines so the user has context
69
+ # (auth failure, provisioning issue, network error, etc.) without re-running verbose.
72
70
  def self.handle_error(errors)
73
71
  errors.each do |error|
74
- if error
75
- if error =~ /Error/
76
- UI.error(error.to_s)
77
- else
78
- UI.verbose(error.to_s)
79
- end
72
+ next unless error
73
+
74
+ if error.include?('Error')
75
+ UI.error(error.to_s)
76
+ else
77
+ UI.verbose(error.to_s)
80
78
  end
81
79
  end
82
80
 
83
- if errors.any? { |e| e =~ /Error/ }
84
- UI.user_error!('Error while calling ta-cli')
85
- elsif errors.any?
86
- UI.user_error!("Error while calling ta-cli: #{errors.join(', ')}")
87
- end
81
+ error_lines = errors.compact.select { |e| e.include?('Error') }
82
+ return if error_lines.empty?
83
+
84
+ UI.user_error!("Error while calling ta-cli: #{error_lines.join(' | ')}")
88
85
  end
89
86
 
90
- # Run the given command
87
+ # Run the given command. Streams stdout to UI.message; routes stderr through
88
+ # handle_error on non-zero exit. Masks --api_token in verbose command echo.
89
+ #
90
+ # Uses Open3.popen3 with the command array form (no shell interpretation), so
91
+ # shell metacharacters in api_token / release_notes / file paths are passed
92
+ # literally to ta-cli without need for manual escaping.
91
93
  def self.call_ta_cli(command)
92
94
  UI.message("Starting ta-cli...")
93
- require 'open3'
94
95
  if FastlaneCore::Globals.verbose?
95
96
  UI.verbose("ta-cli command:\n\n")
96
- UI.command(command.to_s)
97
+ UI.command(redact_command(command).to_s)
97
98
  UI.verbose("\n\n")
98
99
  end
99
- final_command = command.map { |arg| Shellwords.escape(arg) }.join(" ")
100
+
100
101
  out = []
101
102
  error = []
102
- Open3.popen3(final_command) do |stdin, stdout, stderr, wait_thr|
103
- while (line = stdout.gets)
104
- out << line
105
- UI.message(line.strip!) if FastlaneCore::Globals.verbose? && !line.strip.empty?
103
+ out_thread = nil
104
+ err_thread = nil
105
+ Open3.popen3(*command) do |stdin, stdout, stderr, wait_thr|
106
+ stdin.close
107
+
108
+ # Read stdout and stderr concurrently to avoid deadlock when ta-cli
109
+ # fills one buffer before we finish draining the other.
110
+ out_thread = Thread.new do
111
+ while (line = stdout.gets)
112
+ out << line
113
+ UI.message(line.strip) unless line.strip.empty?
114
+ end
106
115
  end
107
- while (line = stderr.gets)
108
- error << line.strip!
116
+
117
+ err_thread = Thread.new do
118
+ while (line = stderr.gets)
119
+ error << line.strip
120
+ end
109
121
  end
122
+
123
+ out_thread.join
124
+ err_thread.join
125
+
110
126
  exit_status = wait_thr.value
111
127
  handle_error(error) unless exit_status.success?
112
128
  end
113
129
  out.join
130
+ ensure
131
+ # If popen3's block raised before threads joined, make sure no readers
132
+ # are left holding pipe FDs.
133
+ out_thread&.kill if out_thread&.alive?
134
+ err_thread&.kill if err_thread&.alive?
114
135
  end
115
136
 
137
+ # Return a copy of the command array with --api_token=... masked.
138
+ def self.redact_command(command)
139
+ command.map do |arg|
140
+ arg.kind_of?(String) && arg.start_with?("--api_token=") ? "--api_token=********" : arg
141
+ end
142
+ end
143
+ private_class_method :redact_command
116
144
  end
117
145
  end
118
146
  end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Testappio
3
- VERSION = "1.0.4"
3
+ VERSION = "2.0.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-testappio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - TestApp.io
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-04-27 00:00:00.000000000 Z
11
+ date: 2026-05-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -30,34 +30,28 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '2.204'
34
- - - ">="
35
- - !ruby/object:Gem::Version
36
- version: 2.204.3
33
+ version: '2.217'
37
34
  type: :development
38
35
  prerelease: false
39
36
  version_requirements: !ruby/object:Gem::Requirement
40
37
  requirements:
41
38
  - - "~>"
42
39
  - !ruby/object:Gem::Version
43
- version: '2.204'
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 2.204.3
40
+ version: '2.217'
47
41
  - !ruby/object:Gem::Dependency
48
42
  name: pry
49
43
  requirement: !ruby/object:Gem::Requirement
50
44
  requirements:
51
45
  - - "~>"
52
46
  - !ruby/object:Gem::Version
53
- version: '0.13'
47
+ version: '0.14'
54
48
  type: :development
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
51
  requirements:
58
52
  - - "~>"
59
53
  - !ruby/object:Gem::Version
60
- version: '0.13'
54
+ version: '0.14'
61
55
  - !ruby/object:Gem::Dependency
62
56
  name: rake
63
57
  requirement: !ruby/object:Gem::Requirement
@@ -92,76 +86,81 @@ dependencies:
92
86
  requirements:
93
87
  - - "~>"
94
88
  - !ruby/object:Gem::Version
95
- version: '0.4'
89
+ version: '0.6'
96
90
  type: :development
97
91
  prerelease: false
98
92
  version_requirements: !ruby/object:Gem::Requirement
99
93
  requirements:
100
94
  - - "~>"
101
95
  - !ruby/object:Gem::Version
102
- version: '0.4'
96
+ version: '0.6'
103
97
  - !ruby/object:Gem::Dependency
104
98
  name: rubocop
105
99
  requirement: !ruby/object:Gem::Requirement
106
100
  requirements:
107
- - - '='
101
+ - - "~>"
108
102
  - !ruby/object:Gem::Version
109
- version: 1.12.1
103
+ version: '1.50'
110
104
  type: :development
111
105
  prerelease: false
112
106
  version_requirements: !ruby/object:Gem::Requirement
113
107
  requirements:
114
- - - '='
108
+ - - "~>"
115
109
  - !ruby/object:Gem::Version
116
- version: 1.12.1
110
+ version: '1.50'
117
111
  - !ruby/object:Gem::Dependency
118
112
  name: rubocop-performance
119
113
  requirement: !ruby/object:Gem::Requirement
120
114
  requirements:
121
115
  - - "~>"
122
116
  - !ruby/object:Gem::Version
123
- version: 1.17.1
117
+ version: '1.20'
124
118
  type: :development
125
119
  prerelease: false
126
120
  version_requirements: !ruby/object:Gem::Requirement
127
121
  requirements:
128
122
  - - "~>"
129
123
  - !ruby/object:Gem::Version
130
- version: 1.17.1
124
+ version: '1.20'
131
125
  - !ruby/object:Gem::Dependency
132
- name: rubocop-require_tools
126
+ name: simplecov
133
127
  requirement: !ruby/object:Gem::Requirement
134
128
  requirements:
135
129
  - - "~>"
136
130
  - !ruby/object:Gem::Version
137
- version: 0.1.2
131
+ version: '0.22'
138
132
  type: :development
139
133
  prerelease: false
140
134
  version_requirements: !ruby/object:Gem::Requirement
141
135
  requirements:
142
136
  - - "~>"
143
137
  - !ruby/object:Gem::Version
144
- version: 0.1.2
138
+ version: '0.22'
145
139
  - !ruby/object:Gem::Dependency
146
- name: simplecov
140
+ name: simplecov-cobertura
147
141
  requirement: !ruby/object:Gem::Requirement
148
142
  requirements:
149
143
  - - "~>"
150
144
  - !ruby/object:Gem::Version
151
- version: '0.21'
145
+ version: '3.1'
152
146
  type: :development
153
147
  prerelease: false
154
148
  version_requirements: !ruby/object:Gem::Requirement
155
149
  requirements:
156
150
  - - "~>"
157
151
  - !ruby/object:Gem::Version
158
- version: '0.21'
159
- description:
152
+ version: '3.1'
153
+ description: |
154
+ A Fastlane plugin that uploads Android (.apk) and iOS (.ipa) builds to TestApp.io,
155
+ notifying your team for testing and feedback. Wraps the ta-cli binary to provide a
156
+ single upload_to_testappio Fastlane action with platform detection, release notes
157
+ from git, and selective team notifications.
160
158
  email: support@testapp.io
161
159
  executables: []
162
160
  extensions: []
163
161
  extra_rdoc_files: []
164
162
  files:
163
+ - CHANGELOG.md
165
164
  - LICENSE
166
165
  - README.md
167
166
  - lib/fastlane/plugin/testappio.rb
@@ -171,8 +170,12 @@ files:
171
170
  homepage: https://github.com/testappio/fastlane-plugin-testappio
172
171
  licenses:
173
172
  - MIT
174
- metadata: {}
175
- post_install_message:
173
+ metadata:
174
+ rubygems_mfa_required: 'true'
175
+ source_code_uri: https://github.com/testappio/fastlane-plugin-testappio
176
+ changelog_uri: https://github.com/testappio/fastlane-plugin-testappio/blob/main/CHANGELOG.md
177
+ bug_tracker_uri: https://github.com/testappio/fastlane-plugin-testappio/issues
178
+ post_install_message:
176
179
  rdoc_options: []
177
180
  require_paths:
178
181
  - lib
@@ -180,15 +183,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
180
183
  requirements:
181
184
  - - ">="
182
185
  - !ruby/object:Gem::Version
183
- version: '2.6'
186
+ version: '3.0'
184
187
  required_rubygems_version: !ruby/object:Gem::Requirement
185
188
  requirements:
186
189
  - - ">="
187
190
  - !ruby/object:Gem::Version
188
191
  version: '0'
189
192
  requirements: []
190
- rubygems_version: 3.4.12
191
- signing_key:
193
+ rubygems_version: 3.4.19
194
+ signing_key:
192
195
  specification_version: 4
193
196
  summary: Deploy your Android & iOS to TestApp.io
194
197
  test_files: []