├── .gitignore ├── .rspec ├── .rubocop.yml ├── .travis.yml ├── Gemfile ├── LICENSE ├── README.md ├── Rakefile ├── circle.yml ├── fastlane-plugin-clean_testflight_testers.gemspec ├── fastlane ├── Fastfile └── Pluginfile ├── lib └── fastlane │ └── plugin │ ├── clean_testflight_testers.rb │ └── clean_testflight_testers │ ├── actions │ └── clean_testflight_testers_action.rb │ ├── helper │ └── clean_testflight_testers_helper.rb │ └── version.rb ├── screenshot.png └── spec └── spec_helper.rb /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | Gemfile.lock 3 | 4 | ## Documentation cache and generated files: 5 | /.yardoc/ 6 | /_yardoc/ 7 | /doc/ 8 | /rdoc/ 9 | fastlane/README.md 10 | fastlane/report.xml 11 | coverage 12 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --color 3 | --format d 4 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/MultipleComparison: 2 | Enabled: false 3 | 4 | Style/PercentLiteralDelimiters: 5 | Enabled: false 6 | 7 | # kind_of? is a good way to check a type 8 | Style/ClassCheck: 9 | EnforcedStyle: kind_of? 10 | 11 | Style/FrozenStringLiteralComment: 12 | Enabled: false 13 | 14 | # This doesn't work with older versions of Ruby (pre 2.4.0) 15 | Style/SafeNavigation: 16 | Enabled: false 17 | 18 | # This doesn't work with older versions of Ruby (pre 2.4.0) 19 | Performance/RegexpMatch: 20 | Enabled: false 21 | 22 | # This suggests use of `tr` instead of `gsub`. While this might be more performant, 23 | # these methods are not at all interchangable, and behave very differently. This can 24 | # lead to people making the substitution without considering the differences. 25 | Performance/StringReplacement: 26 | Enabled: false 27 | 28 | # .length == 0 is also good, we don't always want .zero? 29 | Style/NumericPredicate: 30 | Enabled: false 31 | 32 | # this would cause errors with long lanes 33 | Metrics/BlockLength: 34 | Enabled: false 35 | 36 | # this is a bit buggy 37 | Metrics/ModuleLength: 38 | Enabled: false 39 | 40 | # certificate_1 is an okay variable name 41 | Style/VariableNumber: 42 | Enabled: false 43 | 44 | # This is used a lot across the fastlane code base for config files 45 | Style/MethodMissing: 46 | Enabled: false 47 | 48 | # This rule isn't useful, lots of discussion happening around it also 49 | # e.g. https://github.com/bbatsov/rubocop/issues/2338 50 | MultilineBlockChain: 51 | Enabled: false 52 | 53 | # 54 | # File.chmod(0777, f) 55 | # 56 | # is easier to read than 57 | # 58 | # File.chmod(0o777, f) 59 | # 60 | Style/NumericLiteralPrefix: 61 | Enabled: false 62 | 63 | # 64 | # command = (!clean_expired.nil? || !clean_pattern.nil?) ? CLEANUP : LIST 65 | # 66 | # is easier to read than 67 | # 68 | # command = !clean_expired.nil? || !clean_pattern.nil? ? CLEANUP : LIST 69 | # 70 | Style/TernaryParentheses: 71 | Enabled: false 72 | 73 | # sometimes it is useful to have those empty methods 74 | Style/EmptyMethod: 75 | Enabled: false 76 | 77 | # It's better to be more explicit about the type 78 | Style/BracesAroundHashParameters: 79 | Enabled: false 80 | 81 | # specs sometimes have useless assignments, which is fine 82 | Lint/UselessAssignment: 83 | Exclude: 84 | - '**/spec/**/*' 85 | 86 | # We could potentially enable the 2 below: 87 | Layout/IndentHash: 88 | Enabled: false 89 | 90 | Layout/AlignHash: 91 | Enabled: false 92 | 93 | # HoundCI doesn't like this rule 94 | Layout/DotPosition: 95 | Enabled: false 96 | 97 | # We allow !! as it's an easy way to convert ot boolean 98 | Style/DoubleNegation: 99 | Enabled: false 100 | 101 | # Prevent to replace [] into %i 102 | Style/SymbolArray: 103 | Enabled: false 104 | 105 | # We still support Ruby 2.0.0 106 | Layout/IndentHeredoc: 107 | Enabled: false 108 | 109 | # This cop would not work fine with rspec 110 | Style/MixinGrouping: 111 | Exclude: 112 | - '**/spec/**/*' 113 | 114 | # Sometimes we allow a rescue block that doesn't contain code 115 | Lint/HandleExceptions: 116 | Enabled: false 117 | 118 | # Cop supports --auto-correct. 119 | Lint/UnusedBlockArgument: 120 | Enabled: false 121 | 122 | Lint/AmbiguousBlockAssociation: 123 | Enabled: false 124 | 125 | # Needed for $verbose 126 | Style/GlobalVars: 127 | Enabled: false 128 | 129 | # We want to allow class Fastlane::Class 130 | Style/ClassAndModuleChildren: 131 | Enabled: false 132 | 133 | # $? Exit 134 | Style/SpecialGlobalVars: 135 | Enabled: false 136 | 137 | Metrics/AbcSize: 138 | Enabled: false 139 | 140 | Metrics/MethodLength: 141 | Enabled: false 142 | 143 | Metrics/CyclomaticComplexity: 144 | Enabled: false 145 | 146 | # The %w might be confusing for new users 147 | Style/WordArray: 148 | MinSize: 19 149 | 150 | # raise and fail are both okay 151 | Style/SignalException: 152 | Enabled: false 153 | 154 | # Better too much 'return' than one missing 155 | Style/RedundantReturn: 156 | Enabled: false 157 | 158 | # Having if in the same line might not always be good 159 | Style/IfUnlessModifier: 160 | Enabled: false 161 | 162 | # and and or is okay 163 | Style/AndOr: 164 | Enabled: false 165 | 166 | # Configuration parameters: CountComments. 167 | Metrics/ClassLength: 168 | Max: 320 169 | 170 | 171 | # Configuration parameters: AllowURI, URISchemes. 172 | Metrics/LineLength: 173 | Max: 370 174 | 175 | # Configuration parameters: CountKeywordArgs. 176 | Metrics/ParameterLists: 177 | Max: 17 178 | 179 | Metrics/PerceivedComplexity: 180 | Max: 18 181 | 182 | # Sometimes it's easier to read without guards 183 | Style/GuardClause: 184 | Enabled: false 185 | 186 | # We allow both " and ' 187 | Style/StringLiterals: 188 | Enabled: false 189 | 190 | # something = if something_else 191 | # that's confusing 192 | Style/ConditionalAssignment: 193 | Enabled: false 194 | 195 | # Better to have too much self than missing a self 196 | Style/RedundantSelf: 197 | Enabled: false 198 | 199 | # e.g. 200 | # def self.is_supported?(platform) 201 | # we may never use `platform` 202 | Lint/UnusedMethodArgument: 203 | Enabled: false 204 | 205 | # the let(:key) { ... } 206 | Lint/ParenthesesAsGroupedExpression: 207 | Exclude: 208 | - '**/spec/**/*' 209 | 210 | # This would reject is_ in front of methods 211 | # We use `is_supported?` everywhere already 212 | Style/PredicateName: 213 | Enabled: false 214 | 215 | # We allow the $ 216 | Style/PerlBackrefs: 217 | Enabled: false 218 | 219 | # Disable '+ should be surrounded with a single space' for xcodebuild_spec.rb 220 | Layout/SpaceAroundOperators: 221 | Exclude: 222 | - '**/spec/actions_specs/xcodebuild_spec.rb' 223 | 224 | AllCops: 225 | TargetRubyVersion: 2.0 226 | Include: 227 | - '**/fastlane/Fastfile' 228 | Exclude: 229 | - '**/lib/assets/custom_action_template.rb' 230 | - './vendor/**/*' 231 | 232 | # They have not to be snake_case 233 | Style/FileName: 234 | Exclude: 235 | - '**/Dangerfile' 236 | - '**/Brewfile' 237 | - '**/Gemfile' 238 | - '**/Podfile' 239 | - '**/Rakefile' 240 | - '**/Fastfile' 241 | - '**/Deliverfile' 242 | - '**/Snapfile' 243 | - '**/*.gemspec' 244 | 245 | # We're not there yet 246 | Style/Documentation: 247 | Enabled: false 248 | 249 | # Added after upgrade to 0.38.0 250 | Style/MutableConstant: 251 | Enabled: false 252 | 253 | # length > 0 is good 254 | Style/ZeroLengthPredicate: 255 | Enabled: false 256 | 257 | # Adds complexity 258 | Style/IfInsideElse: 259 | Enabled: false 260 | 261 | # Sometimes we just want to 'collect' 262 | Style/CollectionMethods: 263 | Enabled: false 264 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # os: osx # enable this if you need macOS support 2 | language: ruby 3 | rvm: 4 | - 2.2.4 5 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | gem 'fastlane' 6 | 7 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 8 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Felix Krause 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clean_testflight_testers plugin 2 | 3 | [![fastlane Plugin Badge](https://rawcdn.githack.com/fastlane/fastlane/master/fastlane/assets/plugin-badge.svg)](https://rubygems.org/gems/fastlane-plugin-clean_testflight_testers) 4 | 5 | ## Getting Started 6 | 7 | This project is a [_fastlane_](https://github.com/fastlane/fastlane) plugin. To get started with `fastlane-plugin-clean_testflight_testers`, add it to your project by running: 8 | 9 | ```bash 10 | fastlane add_plugin clean_testflight_testers 11 | ``` 12 | 13 | ## About clean_testflight_testers 14 | 15 | ![screenshot.png](screenshot.png) 16 | 17 | > Automatically remove TestFlight testers that are not actually testing your app 18 | 19 | Just add the following to your `fastlane/Fastfile` 20 | 21 | ```ruby 22 | # Default setup 23 | lane :clean do 24 | clean_testflight_testers 25 | end 26 | 27 | # This won't delete out inactive testers, but just print them 28 | lane :clean do 29 | clean_testflight_testers(dry_run: true) 30 | end 31 | 32 | # Specify a custom number for what's "inactive" 33 | lane :clean do 34 | clean_testflight_testers(days_of_inactivity: 120) # 120 days, so about 4 months 35 | end 36 | 37 | # Provide custom app identifier / username 38 | lane :clean do 39 | clean_testflight_testers(username: "apple@krausefx.com", app_identifier: "best.lane"") 40 | end 41 | ``` 42 | 43 | The plugin will remove all testers that either: 44 | 45 | - Received a TestFlight email, but didn't accept the invite within the last 30 days 46 | - Installed the app within the last 30 days, but didn't launch it once 47 | 48 | Unfortunately the iTunes Connect UI/API doesn't expose the timestamp of the last user session, so we can't really detect the last time someone used the app. The above rules will still help you, remove a big part of inactive testers. 49 | 50 | This plugin could also be smarter, and compare the time stamp of the last build, and compare it with the latest tester activity, feel free to submit a PR for this feature 👍 51 | 52 | ## Issues and Feedback 53 | 54 | Make sure to update to the latest _fastlane_. 55 | 56 | For any other issues and feedback about this plugin, please submit it to this repository. 57 | 58 | ## Troubleshooting 59 | 60 | If you have trouble using plugins, check out the [Plugins Troubleshooting](https://docs.fastlane.tools/plugins/plugins-troubleshooting/) guide. 61 | 62 | ## Using _fastlane_ Plugins 63 | 64 | For more information about how the `fastlane` plugin system works, check out the [Plugins documentation](https://docs.fastlane.tools/plugins/create-plugin/). 65 | 66 | ## About _fastlane_ 67 | 68 | _fastlane_ is the easiest way to automate beta deployments and releases for your iOS and Android apps. To learn more, check out [fastlane.tools](https://fastlane.tools). 69 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | 3 | require 'rspec/core/rake_task' 4 | RSpec::Core::RakeTask.new 5 | 6 | require 'rubocop/rake_task' 7 | RuboCop::RakeTask.new(:rubocop) 8 | 9 | task default: [:spec, :rubocop] 10 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | test: 2 | override: 3 | - bundle exec rake 4 | machine: 5 | ruby: 6 | version: 2.2.4 7 | # Enable xcode below if you need macOS 8 | # xcode: 9 | # version: "7.3" 10 | -------------------------------------------------------------------------------- /fastlane-plugin-clean_testflight_testers.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | 3 | lib = File.expand_path("../lib", __FILE__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'fastlane/plugin/clean_testflight_testers/version' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'fastlane-plugin-clean_testflight_testers' 9 | spec.version = Fastlane::CleanTestflightTesters::VERSION 10 | spec.author = 'Felix Krause' 11 | spec.email = 'testflighttesters@krausefx.com' 12 | 13 | spec.summary = 'Automatically remove TestFlight testers that are not actually testing your app' 14 | spec.homepage = "https://github.com/fastlane-community/fastlane-plugin-clean_testflight_testers" 15 | spec.license = "MIT" 16 | 17 | spec.files = Dir["lib/**/*"] + %w(README.md LICENSE) 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_development_dependency 'pry' 22 | spec.add_development_dependency 'bundler' 23 | spec.add_development_dependency 'rspec' 24 | spec.add_development_dependency 'rake' 25 | spec.add_development_dependency 'rubocop' 26 | spec.add_development_dependency 'simplecov' 27 | spec.add_development_dependency 'fastlane', '>= 2.56.0' 28 | end 29 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | lane :test do 2 | clean_testflight_testers(username: "apple@krausefx.com", app_identifier: "1222374686", dry_run: true) 3 | end 4 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/clean_testflight_testers.rb: -------------------------------------------------------------------------------- 1 | require 'fastlane/plugin/clean_testflight_testers/version' 2 | 3 | module Fastlane 4 | module CleanTestflightTesters 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::CleanTestflightTesters.all_classes.each do |current| 15 | require current 16 | end 17 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/clean_testflight_testers/actions/clean_testflight_testers_action.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Actions 3 | class CleanTestflightTestersAction < Action 4 | def self.run(params) 5 | require 'spaceship' 6 | 7 | app_identifier = params[:app_identifier] 8 | username = params[:username] 9 | 10 | UI.message("Login to iTunes Connect (#{username})") 11 | Spaceship::Tunes.login(username) 12 | Spaceship::Tunes.select_team 13 | UI.message("Login successful") 14 | 15 | UI.message("Fetching all TestFlight testers, this might take a few minutes, depending on the number of testers") 16 | 17 | # Convert from bundle identifier to app ID 18 | spaceship_app ||= Spaceship::ConnectAPI::App.find(app_identifier) 19 | UI.user_error!("Couldn't find app '#{app_identifier}' on the account of '#{username}' on iTunes Connect") unless spaceship_app 20 | 21 | all_testers = spaceship_app.get_beta_testers(includes: "betaTesterMetrics", limit: 200) 22 | counter = 0 23 | 24 | all_testers.each do |current_tester| 25 | tester_metrics = current_tester.beta_tester_metrics.first 26 | 27 | time = Time.parse(tester_metrics.last_modified_date) 28 | days_since_status_change = (Time.now - time) / 60.0 / 60.0 / 24.0 29 | 30 | if tester_metrics.beta_tester_state == "INVITED" 31 | if days_since_status_change > params[:days_of_inactivity] 32 | remove_tester(current_tester, spaceship_app, params[:dry_run]) # user got invited, but never installed a build... why would you do that? 33 | counter += 1 34 | end 35 | else 36 | # We don't really have a good way to detect whether the user is active unfortunately 37 | # So we can just delete users that had no sessions 38 | if days_since_status_change > params[:days_of_inactivity] && tester_metrics.session_count == 0 39 | # User had no sessions in the last e.g. 30 days, let's get rid of them 40 | remove_tester(current_tester, spaceship_app, params[:dry_run]) 41 | counter += 1 42 | elsif params[:oldest_build_allowed] && tester_metrics.installed_cf_bundle_short_version_string.to_i > 0 && tester_metrics.installed_cf_bundle_short_version_string.to_i < params[:oldest_build_allowed] 43 | # User has a build that is too old, let's get rid of them 44 | remove_tester(current_tester, spaceship_app, params[:dry_run]) 45 | counter += 1 46 | end 47 | end 48 | end 49 | 50 | if params[:dry_run] 51 | UI.success("Didn't delete any testers, but instead only printed them out (#{counter}), disable `dry_run` to actually delete them 🦋") 52 | else 53 | UI.success("Successfully removed #{counter} testers 🦋") 54 | end 55 | end 56 | 57 | def self.remove_tester(tester, app, dry_run) 58 | if dry_run 59 | UI.message("TestFlight tester #{tester.email} seems to be inactive for app ID #{app.id}") 60 | else 61 | UI.message("Removing tester #{tester.email} due to inactivity from app ID #{app.id}...") 62 | tester.delete_from_apps(apps: [app]) 63 | end 64 | end 65 | 66 | def self.description 67 | "Automatically remove TestFlight testers that are not actually testing your app" 68 | end 69 | 70 | def self.authors 71 | ["KrauseFx"] 72 | end 73 | 74 | def self.details 75 | "Automatically remove TestFlight testers that are not actually testing your app" 76 | end 77 | 78 | def self.available_options 79 | user = CredentialsManager::AppfileConfig.try_fetch_value(:itunes_connect_id) 80 | user ||= CredentialsManager::AppfileConfig.try_fetch_value(:apple_id) 81 | 82 | [ 83 | FastlaneCore::ConfigItem.new(key: :username, 84 | short_option: "-u", 85 | env_name: "CLEAN_TESTFLIGHT_TESTERS_USERNAME", 86 | description: "Your Apple ID Username", 87 | default_value: user), 88 | FastlaneCore::ConfigItem.new(key: :app_identifier, 89 | short_option: "-a", 90 | env_name: "CLEAN_TESTFLIGHT_TESTERS_APP_IDENTIFIER", 91 | description: "The bundle identifier of the app to upload or manage testers (optional)", 92 | optional: true, 93 | default_value: ENV["TESTFLIGHT_APP_IDENTITIFER"] || CredentialsManager::AppfileConfig.try_fetch_value(:app_identifier)), 94 | FastlaneCore::ConfigItem.new(key: :team_id, 95 | short_option: "-q", 96 | env_name: "CLEAN_TESTFLIGHT_TESTERS_TEAM_ID", 97 | description: "The ID of your iTunes Connect team if you're in multiple teams", 98 | optional: true, 99 | is_string: false, # as we also allow integers, which we convert to strings anyway 100 | default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_id), 101 | verify_block: proc do |value| 102 | ENV["FASTLANE_ITC_TEAM_ID"] = value.to_s 103 | end), 104 | FastlaneCore::ConfigItem.new(key: :team_name, 105 | short_option: "-r", 106 | env_name: "CLEAN_TESTFLIGHT_TESTERS_TEAM_NAME", 107 | description: "The name of your iTunes Connect team if you're in multiple teams", 108 | optional: true, 109 | default_value: CredentialsManager::AppfileConfig.try_fetch_value(:itc_team_name), 110 | verify_block: proc do |value| 111 | ENV["FASTLANE_ITC_TEAM_NAME"] = value.to_s 112 | end), 113 | FastlaneCore::ConfigItem.new(key: :days_of_inactivity, 114 | short_option: "-k", 115 | env_name: "CLEAN_TESTFLIGHT_TESTERS_WAIT_PROCESSING_INTERVAL", 116 | description: "Numbers of days a tester has to be inactive for (no build uses) for them to be removed", 117 | default_value: 30, 118 | type: Integer, 119 | verify_block: proc do |value| 120 | UI.user_error!("Please enter a valid positive number of days") unless value.to_i > 0 121 | end), 122 | FastlaneCore::ConfigItem.new(key: :oldest_build_allowed, 123 | short_option: "-b", 124 | env_name: "CLEAN_TESTFLIGHT_TESTERS_OLDEST_BUILD_ALLOWED", 125 | description: "Oldest build number allowed. All testers with older builds will be removed", 126 | optional: true, 127 | default_value: 0, 128 | type: Integer, 129 | verify_block: proc do |value| 130 | UI.user_error!("Please enter a valid build number") unless value.to_i >= 0 131 | end), 132 | FastlaneCore::ConfigItem.new(key: :dry_run, 133 | short_option: "-d", 134 | env_name: "CLEAN_TESTFLIGHT_TESTERS_DRY_RUN", 135 | description: "Only print inactive users, don't delete them", 136 | default_value: false, 137 | is_string: false) 138 | ] 139 | end 140 | 141 | def self.is_supported?(platform) 142 | platform == :ios 143 | end 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/clean_testflight_testers/helper/clean_testflight_testers_helper.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module Helper 3 | class CleanTestflightTestersHelper 4 | end 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/fastlane/plugin/clean_testflight_testers/version.rb: -------------------------------------------------------------------------------- 1 | module Fastlane 2 | module CleanTestflightTesters 3 | VERSION = "0.3.0" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fastlane-community/fastlane-plugin-clean_testflight_testers/29f04e084c38d021b2b2590de750bc9bf20c7a04/screenshot.png -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__) 2 | 3 | require 'simplecov' 4 | 5 | # SimpleCov.minimum_coverage 95 6 | SimpleCov.start 7 | 8 | # This module is only used to check the environment is currently a testing env 9 | module SpecHelper 10 | end 11 | 12 | require 'fastlane' # to import the Action super class 13 | require 'fastlane/plugin/clean_testflight_testers' # import the actual plugin 14 | 15 | Fastlane.load_actions # load other actions (in case your plugin calls other actions or shared values) 16 | --------------------------------------------------------------------------------