fastlane 2.178.0 → 2.182.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.
Files changed (148) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +97 -84
  4. data/cert/lib/cert/commands_generator.rb +2 -1
  5. data/cert/lib/cert/options.rb +1 -0
  6. data/cert/lib/cert/runner.rb +4 -0
  7. data/deliver/lib/deliver/commands_generator.rb +2 -1
  8. data/deliver/lib/deliver/download_screenshots.rb +1 -2
  9. data/deliver/lib/deliver/languages.rb +1 -1
  10. data/deliver/lib/deliver/options.rb +3 -2
  11. data/deliver/lib/deliver/runner.rb +4 -0
  12. data/deliver/lib/deliver/setup.rb +0 -1
  13. data/deliver/lib/deliver/upload_metadata.rb +2 -1
  14. data/fastlane/lib/fastlane/actions/actions_helper.rb +2 -2
  15. data/fastlane/lib/fastlane/actions/app_store_build_number.rb +5 -0
  16. data/fastlane/lib/fastlane/actions/app_store_connect_api_key.rb +3 -3
  17. data/fastlane/lib/fastlane/actions/backup_xcarchive.rb +1 -1
  18. data/fastlane/lib/fastlane/actions/build_app.rb +4 -0
  19. data/fastlane/lib/fastlane/actions/check_app_store_metadata.rb +4 -0
  20. data/fastlane/lib/fastlane/actions/clipboard.rb +3 -6
  21. data/fastlane/lib/fastlane/actions/docs/capture_ios_screenshots.md +1 -1
  22. data/fastlane/lib/fastlane/actions/docs/frame_screenshots.md +18 -1
  23. data/fastlane/lib/fastlane/actions/docs/upload_to_play_store.md +2 -1
  24. data/fastlane/lib/fastlane/actions/ensure_env_vars.rb +2 -6
  25. data/fastlane/lib/fastlane/actions/get_provisioning_profile.rb +4 -0
  26. data/fastlane/lib/fastlane/actions/get_version_number.rb +17 -10
  27. data/fastlane/lib/fastlane/actions/git_branch.rb +4 -10
  28. data/fastlane/lib/fastlane/actions/git_commit.rb +3 -1
  29. data/fastlane/lib/fastlane/actions/git_submodule_update.rb +16 -8
  30. data/fastlane/lib/fastlane/actions/git_tag_exists.rb +4 -0
  31. data/fastlane/lib/fastlane/actions/import_from_git.rb +5 -5
  32. data/fastlane/lib/fastlane/actions/install_provisioning_profile.rb +4 -0
  33. data/fastlane/lib/fastlane/actions/jira.rb +61 -14
  34. data/fastlane/lib/fastlane/actions/latest_testflight_build_number.rb +1 -0
  35. data/fastlane/lib/fastlane/actions/match_nuke.rb +59 -0
  36. data/fastlane/lib/fastlane/actions/notarize.rb +98 -51
  37. data/fastlane/lib/fastlane/actions/slack.rb +155 -133
  38. data/fastlane/lib/fastlane/actions/sourcedocs.rb +164 -0
  39. data/fastlane/lib/fastlane/actions/spaceship_logs.rb +1 -1
  40. data/fastlane/lib/fastlane/actions/update_project_provisioning.rb +1 -2
  41. data/fastlane/lib/fastlane/actions/upload_symbols_to_crashlytics.rb +4 -2
  42. data/fastlane/lib/fastlane/cli_tools_distributor.rb +1 -1
  43. data/fastlane/lib/fastlane/commands_generator.rb +2 -1
  44. data/fastlane/lib/fastlane/fast_file.rb +10 -2
  45. data/fastlane/lib/fastlane/fastlane_require.rb +7 -1
  46. data/fastlane/lib/fastlane/helper/git_helper.rb +19 -7
  47. data/fastlane/lib/fastlane/lane_manager.rb +3 -2
  48. data/fastlane/lib/fastlane/notification/slack.rb +56 -0
  49. data/fastlane/lib/fastlane/plugins/plugin_fetcher.rb +1 -2
  50. data/fastlane/lib/fastlane/plugins/plugin_info.rb +2 -2
  51. data/fastlane/lib/fastlane/plugins/plugin_info_collector.rb +1 -2
  52. data/fastlane/lib/fastlane/plugins/plugin_manager.rb +1 -2
  53. data/fastlane/lib/fastlane/plugins/template/%gem_name%.gemspec.erb +7 -6
  54. data/fastlane/lib/fastlane/plugins/template/.rubocop.yml +30 -35
  55. data/fastlane/lib/fastlane/plugins/template/spec/spec_helper.rb.erb +1 -1
  56. data/fastlane/lib/fastlane/setup/setup.rb +23 -10
  57. data/fastlane/lib/fastlane/swift_fastlane_function.rb +39 -14
  58. data/fastlane/lib/fastlane/swift_runner_upgrader.rb +2 -0
  59. data/fastlane/lib/fastlane/version.rb +2 -2
  60. data/fastlane/swift/Deliverfile.swift +1 -1
  61. data/fastlane/swift/DeliverfileProtocol.swift +3 -3
  62. data/fastlane/swift/Fastlane.swift +6852 -3824
  63. data/fastlane/swift/FastlaneSwiftRunner/FastlaneSwiftRunner.xcodeproj/project.pbxproj +4 -0
  64. data/fastlane/swift/Gymfile.swift +1 -1
  65. data/fastlane/swift/GymfileProtocol.swift +1 -1
  66. data/fastlane/swift/LaneFileProtocol.swift +9 -3
  67. data/fastlane/swift/Matchfile.swift +1 -1
  68. data/fastlane/swift/MatchfileProtocol.swift +1 -1
  69. data/fastlane/swift/OptionalConfigValue.swift +131 -0
  70. data/fastlane/swift/Precheckfile.swift +1 -1
  71. data/fastlane/swift/PrecheckfileProtocol.swift +3 -3
  72. data/fastlane/swift/RubyCommand.swift +1 -1
  73. data/fastlane/swift/Scanfile.swift +1 -1
  74. data/fastlane/swift/ScanfileProtocol.swift +5 -1
  75. data/fastlane/swift/Screengrabfile.swift +1 -1
  76. data/fastlane/swift/ScreengrabfileProtocol.swift +1 -1
  77. data/fastlane/swift/Snapshotfile.swift +1 -1
  78. data/fastlane/swift/SnapshotfileProtocol.swift +5 -1
  79. data/fastlane/swift/SocketClient.swift +2 -1
  80. data/fastlane/swift/SocketResponse.swift +4 -2
  81. data/fastlane/swift/formatting/Brewfile.lock.json +18 -16
  82. data/fastlane/swift/upgrade_manifest.json +1 -1
  83. data/fastlane_core/lib/fastlane_core.rb +22 -21
  84. data/fastlane_core/lib/fastlane_core/build_watcher.rb +50 -9
  85. data/fastlane_core/lib/fastlane_core/clipboard.rb +20 -0
  86. data/fastlane_core/lib/fastlane_core/configuration/configuration.rb +5 -3
  87. data/fastlane_core/lib/fastlane_core/helper.rb +28 -5
  88. data/fastlane_core/lib/fastlane_core/languages.rb +2 -2
  89. data/fastlane_core/lib/fastlane_core/queue_worker.rb +2 -2
  90. data/fastlane_core/lib/fastlane_core/swag.rb +1 -1
  91. data/fastlane_core/lib/fastlane_core/ui/help.erb +35 -0
  92. data/fastlane_core/lib/fastlane_core/ui/help_formatter.rb +16 -0
  93. data/fastlane_core/lib/fastlane_core/ui/implementations/shell.rb +12 -1
  94. data/frameit/lib/frameit/commands_generator.rb +2 -1
  95. data/gym/lib/gym/commands_generator.rb +2 -1
  96. data/gym/lib/gym/generators/package_command_generator.rb +4 -0
  97. data/gym/lib/gym/generators/package_command_generator_xcode7.rb +13 -8
  98. data/gym/lib/gym/runner.rb +15 -4
  99. data/match/lib/match/change_password.rb +3 -3
  100. data/match/lib/match/commands_generator.rb +2 -1
  101. data/match/lib/match/encryption/interface.rb +1 -1
  102. data/match/lib/match/encryption/openssl.rb +2 -2
  103. data/match/lib/match/module.rb +1 -0
  104. data/pem/lib/pem/commands_generator.rb +2 -1
  105. data/pilot/lib/pilot/commands_generator.rb +2 -1
  106. data/pilot/lib/pilot/manager.rb +4 -0
  107. data/pilot/lib/pilot/options.rb +3 -2
  108. data/pilot/lib/pilot/tester_exporter.rb +0 -1
  109. data/pilot/lib/pilot/tester_manager.rb +0 -1
  110. data/precheck/lib/precheck/commands_generator.rb +2 -1
  111. data/precheck/lib/precheck/options.rb +1 -0
  112. data/precheck/lib/precheck/runner.rb +4 -0
  113. data/produce/lib/produce/commands_generator.rb +2 -1
  114. data/scan/lib/scan/commands_generator.rb +2 -1
  115. data/scan/lib/scan/options.rb +10 -5
  116. data/scan/lib/scan/runner.rb +54 -1
  117. data/scan/lib/scan/test_command_generator.rb +10 -8
  118. data/screengrab/lib/screengrab/android_environment.rb +6 -4
  119. data/screengrab/lib/screengrab/commands_generator.rb +2 -1
  120. data/screengrab/lib/screengrab/runner.rb +1 -1
  121. data/sigh/lib/sigh/commands_generator.rb +2 -1
  122. data/sigh/lib/sigh/options.rb +1 -0
  123. data/sigh/lib/sigh/runner.rb +4 -0
  124. data/snapshot/lib/assets/SnapfileTemplate +1 -1
  125. data/snapshot/lib/assets/SnapshotHelper.swift +1 -1
  126. data/snapshot/lib/snapshot/commands_generator.rb +3 -1
  127. data/snapshot/lib/snapshot/options.rb +5 -0
  128. data/snapshot/lib/snapshot/reports_generator.rb +4 -0
  129. data/snapshot/lib/snapshot/simulator_launchers/launcher_configuration.rb +2 -0
  130. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher.rb +1 -1
  131. data/snapshot/lib/snapshot/simulator_launchers/simulator_launcher_base.rb +8 -4
  132. data/spaceship/README.md +2 -12
  133. data/spaceship/lib/spaceship/base.rb +2 -2
  134. data/spaceship/lib/spaceship/commands_generator.rb +4 -2
  135. data/spaceship/lib/spaceship/connect_api/models/app_screenshot.rb +1 -1
  136. data/spaceship/lib/spaceship/connect_api/models/profile.rb +6 -0
  137. data/spaceship/lib/spaceship/connect_api/provisioning/provisioning.rb +6 -2
  138. data/spaceship/lib/spaceship/connect_api/token.rb +7 -1
  139. data/spaceship/lib/spaceship/spaceauth_runner.rb +19 -9
  140. data/spaceship/lib/spaceship/tunes/members.rb +1 -1
  141. data/spaceship/lib/spaceship/ui.rb +2 -2
  142. data/supply/lib/supply/client.rb +3 -1
  143. data/supply/lib/supply/commands_generator.rb +2 -1
  144. data/supply/lib/supply/options.rb +2 -2
  145. data/supply/lib/supply/uploader.rb +1 -0
  146. metadata +53 -64
  147. data/gym/lib/gym/.runner.rb.swp +0 -0
  148. data/pilot/lib/pilot/tester_util.rb +0 -0
@@ -70,6 +70,7 @@ module Fastlane
70
70
  short_option: "-u",
71
71
  env_name: "ITUNESCONNECT_USER",
72
72
  description: "Your Apple ID Username",
73
+ optional: true,
73
74
  default_value: user,
74
75
  default_value_dynamic: true),
75
76
  FastlaneCore::ConfigItem.new(key: :version,
@@ -0,0 +1,59 @@
1
+ module Fastlane
2
+ module Actions
3
+ class MatchNukeAction < Action
4
+ def self.run(params)
5
+ require 'match'
6
+
7
+ params.load_configuration_file("Matchfile")
8
+ params[:api_key] ||= Actions.lane_context[SharedValues::APP_STORE_CONNECT_API_KEY]
9
+
10
+ cert_type = Match.cert_type_sym(params[:type])
11
+ UI.important("Going to revoke your '#{cert_type}' certificate type and provisioning profiles")
12
+
13
+ Match::Nuke.new.run(params, type: cert_type)
14
+ end
15
+
16
+ #####################################################
17
+ # @!group Documentation
18
+ #####################################################
19
+
20
+ def self.description
21
+ "Easily nuke your certificate and provisioning profiles (via _match_)"
22
+ end
23
+
24
+ def self.details
25
+ [
26
+ "Use the match_nuke action to revoke your certificates and provisioning profiles.",
27
+ "Don't worry, apps that are already available in the App Store / TestFlight will still work.",
28
+ "Builds distributed via Ad Hoc or Enterprise will be disabled after nuking your account, so you'll have to re-upload a new build.",
29
+ "After clearing your account you'll start from a clean state, and you can run match to generate your certificates and profiles again.",
30
+ "More information: https://docs.fastlane.tools/actions/match/"
31
+ ].join("\n")
32
+ end
33
+
34
+ def self.available_options
35
+ require 'match'
36
+ Match::Options.available_options
37
+ end
38
+
39
+ def self.is_supported?(platform)
40
+ [:ios, :mac].include?(platform)
41
+ end
42
+
43
+ def self.example_code
44
+ [
45
+ 'match_nuke(type: "development")', # See all other options https://github.com/fastlane/fastlane/blob/master/match/lib/match/module.rb#L23
46
+ 'match_nuke(type: "development", api_key: app_store_connect_api_key)'
47
+ ]
48
+ end
49
+
50
+ def self.authors
51
+ ["crazymanish"]
52
+ end
53
+
54
+ def self.category
55
+ :code_signing
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,12 +1,14 @@
1
1
  module Fastlane
2
2
  module Actions
3
3
  class NotarizeAction < Action
4
+ # rubocop:disable Metrics/PerceivedComplexity
4
5
  def self.run(params)
5
6
  package_path = params[:package]
6
7
  bundle_id = params[:bundle_id]
7
8
  try_early_stapling = params[:try_early_stapling]
8
9
  print_log = params[:print_log]
9
10
  verbose = params[:verbose]
11
+ api_key_path = params[:api_key_path]
10
12
 
11
13
  # Compress and read bundle identifier only for .app bundle.
12
14
  compressed_package_path = nil
@@ -28,68 +30,73 @@ module Fastlane
28
30
 
29
31
  UI.user_error!('Could not read bundle identifier, provide as a parameter') unless bundle_id
30
32
 
31
- apple_id_account = CredentialsManager::AccountManager.new(user: params[:username])
32
-
33
- # Add password as a temporary environment variable for altool.
34
- # Use app specific password if specified.
35
- ENV['FL_NOTARIZE_PASSWORD'] = ENV['FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD'] || apple_id_account.password
36
-
37
33
  UI.message('Uploading package to notarization service, might take a while')
38
34
 
39
- notarization_upload_command = "xcrun altool --notarize-app -t osx -f \"#{compressed_package_path || package_path}\" --primary-bundle-id #{bundle_id} -u #{apple_id_account.user} -p @env:FL_NOTARIZE_PASSWORD --output-format xml"
40
- notarization_upload_command << " --asc-provider \"#{params[:asc_provider]}\"" if params[:asc_provider]
35
+ notarization_upload_command = "xcrun altool --notarize-app -t osx -f \"#{compressed_package_path || package_path}\" --primary-bundle-id #{bundle_id} --output-format xml"
41
36
 
42
- notarization_upload_response = Actions.sh(
43
- notarization_upload_command,
44
- log: verbose
45
- )
37
+ notarization_info = {}
38
+ with_notarize_authenticator(params, api_key_path) do |notarize_authenticator|
39
+ notarization_upload_command << " --asc-provider \"#{params[:asc_provider]}\"" if params[:asc_provider] && api_key_path.nil?
46
40
 
47
- FileUtils.rm_rf(compressed_package_path) if compressed_package_path
41
+ notarization_upload_response = Actions.sh(
42
+ notarize_authenticator.call(notarization_upload_command),
43
+ log: verbose
44
+ )
48
45
 
49
- notarization_upload_plist = Plist.parse_xml(notarization_upload_response)
50
- notarization_request_id = notarization_upload_plist['notarization-upload']['RequestUUID']
46
+ FileUtils.rm_rf(compressed_package_path) if compressed_package_path
51
47
 
52
- UI.success("Successfully uploaded package to notarization service with request identifier #{notarization_request_id}")
48
+ notarization_upload_plist = Plist.parse_xml(notarization_upload_response)
53
49
 
54
- notarization_info = {}
55
- while notarization_info.empty? || (notarization_info['Status'] == 'in progress')
56
- if notarization_info.empty?
57
- UI.message('Waiting to query request status')
58
- elsif try_early_stapling
59
- UI.message('Request in progress, trying early staple')
60
-
61
- begin
62
- self.staple(package_path, verbose)
63
- UI.message('Successfully notarized and early stapled package.')
64
-
65
- return
66
- rescue
67
- UI.message('Early staple failed, waiting to query again')
68
- end
50
+ if notarization_upload_plist.key?('product-errors') && notarization_upload_plist['product-errors'].any?
51
+ UI.important("🚫 Could not upload package to notarization service! Here are the reasons:")
52
+ notarization_upload_plist['product-errors'].each { |product_error| UI.error("#{product_error['message']} (#{product_error['code']})") }
53
+ UI.user_error!("Package upload to notarization service cancelled. Please check the error messages above.")
69
54
  end
70
55
 
71
- sleep(30)
72
-
73
- UI.message('Querying request status')
74
-
75
- # As of July 2020, the request UUID might not be available for polling yet which returns an error code
76
- # This is now handled with the error_callback (which prevents an error from being raised)
77
- # Catching this error allows for polling to continue until the notarization is complete
78
- error = false
79
- notarization_info_response = Actions.sh(
80
- "xcrun altool --notarization-info #{notarization_request_id} -u #{apple_id_account.user} -p @env:FL_NOTARIZE_PASSWORD --output-format xml",
81
- log: verbose,
82
- error_callback: lambda { |msg|
83
- error = true
84
- UI.error("Error polling for notarization info: #{msg}")
85
- }
86
- )
56
+ notarization_request_id = notarization_upload_plist['notarization-upload']['RequestUUID']
57
+
58
+ UI.success("Successfully uploaded package to notarization service with request identifier #{notarization_request_id}")
59
+
60
+ while notarization_info.empty? || (notarization_info['Status'] == 'in progress')
61
+ if notarization_info.empty?
62
+ UI.message('Waiting to query request status')
63
+ elsif try_early_stapling
64
+ UI.message('Request in progress, trying early staple')
65
+
66
+ begin
67
+ self.staple(package_path, verbose)
68
+ UI.message('Successfully notarized and early stapled package.')
69
+
70
+ return
71
+ rescue
72
+ UI.message('Early staple failed, waiting to query again')
73
+ end
74
+ end
87
75
 
88
- unless error
89
- notarization_info_plist = Plist.parse_xml(notarization_info_response)
90
- notarization_info = notarization_info_plist['notarization-info']
76
+ sleep(30)
77
+
78
+ UI.message('Querying request status')
79
+
80
+ # As of July 2020, the request UUID might not be available for polling yet which returns an error code
81
+ # This is now handled with the error_callback (which prevents an error from being raised)
82
+ # Catching this error allows for polling to continue until the notarization is complete
83
+ error = false
84
+ notarization_info_response = Actions.sh(
85
+ notarize_authenticator.call("xcrun altool --notarization-info #{notarization_request_id} --output-format xml"),
86
+ log: verbose,
87
+ error_callback: lambda { |msg|
88
+ error = true
89
+ UI.error("Error polling for notarization info: #{msg}")
90
+ }
91
+ )
92
+
93
+ unless error
94
+ notarization_info_plist = Plist.parse_xml(notarization_info_response)
95
+ notarization_info = notarization_info_plist['notarization-info']
96
+ end
91
97
  end
92
98
  end
99
+ # rubocop:enable Metrics/PerceivedComplexity
93
100
 
94
101
  log_url = notarization_info['LogFileURL']
95
102
  ENV['FL_NOTARIZE_LOG_FILE_URL'] = log_url
@@ -123,6 +130,35 @@ module Fastlane
123
130
  )
124
131
  end
125
132
 
133
+ def self.with_notarize_authenticator(params, api_key_path)
134
+ if api_key_path
135
+ # From xcrun altool for --apiKey:
136
+ # This option will search the following directories in sequence for a private key file with the name of 'AuthKey_<api_key>.p8': './private_keys', '~/private_keys', '~/.private_keys', and '~/.appstoreconnect/private_keys'.
137
+ api_key = Spaceship::ConnectAPI::Token.from_json_file(api_key_path)
138
+ api_key_folder_path = File.expand_path('~/.appstoreconnect/private_keys')
139
+ api_key_file_path = File.join(api_key_folder_path, "AuthKey_#{api_key.key_id}.p8")
140
+ directory_exists = File.directory?(api_key_folder_path)
141
+ file_exists = File.exist?(api_key_file_path)
142
+ begin
143
+ FileUtils.mkdir_p(api_key_folder_path) unless directory_exists
144
+ api_key.write_key_to_file(api_key_file_path) unless file_exists
145
+
146
+ yield(proc { |command| "#{command} --apiKey #{api_key.key_id} --apiIssuer #{api_key.issuer_id}" })
147
+ ensure
148
+ FileUtils.rm(api_key_file_path) unless file_exists
149
+ FileUtils.rm_r(api_key_folder_path) unless directory_exists
150
+ end
151
+ else
152
+ apple_id_account = CredentialsManager::AccountManager.new(user: params[:username])
153
+
154
+ # Add password as a temporary environment variable for altool.
155
+ # Use app specific password if specified.
156
+ ENV['FL_NOTARIZE_PASSWORD'] = ENV['FASTLANE_APPLE_APPLICATION_SPECIFIC_PASSWORD'] || apple_id_account.password
157
+
158
+ yield(proc { |command| "#{command} -u #{apple_id_account.user} -p @env:FL_NOTARIZE_PASSWORD" })
159
+ end
160
+ end
161
+
126
162
  def self.description
127
163
  'Notarizes a macOS app'
128
164
  end
@@ -160,6 +196,8 @@ module Fastlane
160
196
  env_name: 'FL_NOTARIZE_USERNAME',
161
197
  description: 'Apple ID username',
162
198
  default_value: username,
199
+ optional: true,
200
+ conflicting_options: [:api_key_path],
163
201
  default_value_dynamic: true),
164
202
  FastlaneCore::ConfigItem.new(key: :asc_provider,
165
203
  env_name: 'FL_NOTARIZE_ASC_PROVIDER',
@@ -177,7 +215,16 @@ module Fastlane
177
215
  description: 'Whether to log requests',
178
216
  optional: true,
179
217
  default_value: false,
180
- type: Boolean)
218
+ type: Boolean),
219
+ FastlaneCore::ConfigItem.new(key: :api_key_path,
220
+ env_name: 'FL_NOTARIZE_API_KEY_PATH',
221
+ description: 'Path to AppStore Connect API key',
222
+ optional: true,
223
+ conflicting_options: [:username],
224
+ is_string: true,
225
+ verify_block: proc do |value|
226
+ UI.user_error!("API Key not found at '#{value}'") unless File.exist?(value)
227
+ end)
181
228
  ]
182
229
  end
183
230
 
@@ -1,70 +1,178 @@
1
+ require 'fastlane/notification/slack'
2
+
1
3
  # rubocop:disable Style/CaseEquality
2
4
  # rubocop:disable Style/MultilineTernaryOperator
3
5
  # rubocop:disable Style/NestedTernaryOperator
4
6
  module Fastlane
5
7
  module Actions
6
8
  class SlackAction < Action
7
- def self.is_supported?(platform)
8
- true
9
- end
9
+ class Runner
10
+ def initialize(slack_url)
11
+ @notifier = Fastlane::Notification::Slack.new(slack_url)
12
+ end
10
13
 
11
- # As there is a text limit in the notifications, we are
12
- # usually interested in the last part of the message
13
- # e.g. for tests
14
- def self.trim_message(message)
15
- # We want the last 7000 characters, instead of the first 7000, as the error is at the bottom
16
- start_index = [message.length - 7000, 0].max
17
- message = message[start_index..-1]
18
- # We want line breaks to be shown on slack output so we replace
19
- # input non-interpreted line break with interpreted line break
20
- message.gsub('\n', "\n")
21
- end
14
+ def run(options)
15
+ options[:message] = self.class.trim_message(options[:message].to_s || '')
16
+ options[:message] = Fastlane::Notification::Slack::LinkConverter.convert(options[:message])
22
17
 
23
- def self.run(options)
24
- require 'slack-notifier'
18
+ options[:pretext] = options[:pretext].gsub('\n', "\n") unless options[:pretext].nil?
25
19
 
26
- options[:message] = self.trim_message(options[:message].to_s || '')
27
- options[:message] = Slack::Notifier::Util::LinkFormatter.format(options[:message])
20
+ if options[:channel].to_s.length > 0
21
+ channel = options[:channel]
22
+ channel = ('#' + options[:channel]) unless ['#', '@'].include?(channel[0]) # send message to channel by default
23
+ end
28
24
 
29
- options[:pretext] = options[:pretext].gsub('\n', "\n") unless options[:pretext].nil?
25
+ username = options[:use_webhook_configured_username_and_icon] ? nil : options[:username]
30
26
 
31
- if options[:channel].to_s.length > 0
32
- channel = options[:channel]
33
- channel = ('#' + options[:channel]) unless ['#', '@'].include?(channel[0]) # send message to channel by default
27
+ slack_attachment = self.class.generate_slack_attachments(options)
28
+ link_names = options[:link_names]
29
+ icon_url = options[:use_webhook_configured_username_and_icon] ? nil : options[:icon_url]
30
+
31
+ post_message(
32
+ channel: channel,
33
+ username: username,
34
+ attachments: [slack_attachment],
35
+ link_names: link_names,
36
+ icon_url: icon_url,
37
+ fail_on_error: options[:fail_on_error]
38
+ )
34
39
  end
35
40
 
36
- username = options[:use_webhook_configured_username_and_icon] ? nil : options[:username]
41
+ def post_message(channel:, username:, attachments:, link_names:, icon_url:, fail_on_error:)
42
+ @notifier.post_to_legacy_incoming_webhook(
43
+ channel: channel,
44
+ username: username,
45
+ link_names: link_names,
46
+ icon_url: icon_url,
47
+ attachments: attachments
48
+ )
49
+ UI.success('Successfully sent Slack notification')
50
+ rescue => error
51
+ UI.error("Exception: #{error}")
52
+ message = "Error pushing Slack message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile, this is usually caused by a misspelled or changed group/channel name or an expired SLACK_URL"
53
+ if fail_on_error
54
+ UI.user_error!(message)
55
+ else
56
+ UI.error(message)
57
+ end
58
+ end
37
59
 
38
- notifier = Slack::Notifier.new(options[:slack_url], channel: channel, username: username)
60
+ # As there is a text limit in the notifications, we are
61
+ # usually interested in the last part of the message
62
+ # e.g. for tests
63
+ def self.trim_message(message)
64
+ # We want the last 7000 characters, instead of the first 7000, as the error is at the bottom
65
+ start_index = [message.length - 7000, 0].max
66
+ message = message[start_index..-1]
67
+ # We want line breaks to be shown on slack output so we replace
68
+ # input non-interpreted line break with interpreted line break
69
+ message.gsub('\n', "\n")
70
+ end
39
71
 
40
- link_names = options[:link_names]
72
+ def self.generate_slack_attachments(options)
73
+ color = (options[:success] ? 'good' : 'danger')
74
+ should_add_payload = ->(payload_name) { options[:default_payloads].map(&:to_sym).include?(payload_name.to_sym) }
41
75
 
42
- icon_url = options[:use_webhook_configured_username_and_icon] ? nil : options[:icon_url]
76
+ slack_attachment = {
77
+ fallback: options[:message],
78
+ text: options[:message],
79
+ pretext: options[:pretext],
80
+ color: color,
81
+ mrkdwn_in: ["pretext", "text", "fields", "message"],
82
+ fields: []
83
+ }
43
84
 
44
- slack_attachment = generate_slack_attachments(options)
85
+ # custom user payloads
86
+ slack_attachment[:fields] += options[:payload].map do |k, v|
87
+ {
88
+ title: k.to_s,
89
+ value: Fastlane::Notification::Slack::LinkConverter.convert(v.to_s),
90
+ short: false
91
+ }
92
+ end
45
93
 
46
- return [notifier, slack_attachment] if Helper.test? # tests will verify the slack attachments and other properties
94
+ # Add the lane to the Slack message
95
+ # This might be nil, if slack is called as "one-off" action
96
+ if should_add_payload[:lane] && Actions.lane_context[Actions::SharedValues::LANE_NAME]
97
+ slack_attachment[:fields] << {
98
+ title: 'Lane',
99
+ value: Actions.lane_context[Actions::SharedValues::LANE_NAME],
100
+ short: true
101
+ }
102
+ end
47
103
 
48
- begin
49
- results = notifier.ping('', link_names: link_names, icon_url: icon_url, attachments: [slack_attachment])
50
- rescue => exception
51
- UI.error("Exception: #{exception}")
52
- ensure
53
- result = results.first if results
54
- if !result.nil? && result.code.to_i == 200
55
- UI.success('Successfully sent Slack notification')
56
- else
57
- UI.verbose(result) unless result.nil?
58
- message = "Error pushing Slack message, maybe the integration has no permission to post on this channel? Try removing the channel parameter in your Fastfile, this is usually caused by a misspelled or changed group/channel name or an expired SLACK_URL"
59
- if options[:fail_on_error]
60
- UI.user_error!(message)
104
+ # test_result
105
+ if should_add_payload[:test_result]
106
+ slack_attachment[:fields] << {
107
+ title: 'Result',
108
+ value: (options[:success] ? 'Success' : 'Error'),
109
+ short: true
110
+ }
111
+ end
112
+
113
+ # git branch
114
+ if Actions.git_branch && should_add_payload[:git_branch]
115
+ slack_attachment[:fields] << {
116
+ title: 'Git Branch',
117
+ value: Actions.git_branch,
118
+ short: true
119
+ }
120
+ end
121
+
122
+ # git_author
123
+ if Actions.git_author_email && should_add_payload[:git_author]
124
+ if FastlaneCore::Env.truthy?('FASTLANE_SLACK_HIDE_AUTHOR_ON_SUCCESS') && options[:success]
125
+ # We only show the git author if the build failed
61
126
  else
62
- UI.error(message)
127
+ slack_attachment[:fields] << {
128
+ title: 'Git Author',
129
+ value: Actions.git_author_email,
130
+ short: true
131
+ }
63
132
  end
64
133
  end
134
+
135
+ # last_git_commit
136
+ if Actions.last_git_commit_message && should_add_payload[:last_git_commit]
137
+ slack_attachment[:fields] << {
138
+ title: 'Git Commit',
139
+ value: Actions.last_git_commit_message,
140
+ short: false
141
+ }
142
+ end
143
+
144
+ # last_git_commit_hash
145
+ if Actions.last_git_commit_hash(true) && should_add_payload[:last_git_commit_hash]
146
+ slack_attachment[:fields] << {
147
+ title: 'Git Commit Hash',
148
+ value: Actions.last_git_commit_hash(short: true),
149
+ short: false
150
+ }
151
+ end
152
+
153
+ # merge additional properties
154
+ deep_merge(slack_attachment, options[:attachment_properties])
155
+ end
156
+
157
+ # Adapted from https://stackoverflow.com/a/30225093/158525
158
+ def self.deep_merge(a, b)
159
+ merger = proc do |key, v1, v2|
160
+ Hash === v1 && Hash === v2 ?
161
+ v1.merge(v2, &merger) : Array === v1 && Array === v2 ?
162
+ v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2
163
+ end
164
+ a.merge(b, &merger)
65
165
  end
66
166
  end
67
167
 
168
+ def self.is_supported?(platform)
169
+ true
170
+ end
171
+
172
+ def self.run(options)
173
+ Runner.new(options[:slack_url]).run(options)
174
+ end
175
+
68
176
  def self.description
69
177
  "Send a success/error message to your [Slack](https://slack.com) group"
70
178
  end
@@ -185,99 +293,13 @@ module Fastlane
185
293
  # @!group Helper
186
294
  #####################################################
187
295
 
188
- def self.generate_slack_attachments(options)
189
- color = (options[:success] ? 'good' : 'danger')
190
- should_add_payload = ->(payload_name) { options[:default_payloads].map(&:to_sym).include?(payload_name.to_sym) }
191
-
192
- slack_attachment = {
193
- fallback: options[:message],
194
- text: options[:message],
195
- pretext: options[:pretext],
196
- color: color,
197
- mrkdwn_in: ["pretext", "text", "fields", "message"],
198
- fields: []
199
- }
200
-
201
- # custom user payloads
202
- slack_attachment[:fields] += options[:payload].map do |k, v|
203
- {
204
- title: k.to_s,
205
- value: Slack::Notifier::Util::LinkFormatter.format(v.to_s),
206
- short: false
207
- }
208
- end
209
-
210
- # Add the lane to the Slack message
211
- # This might be nil, if slack is called as "one-off" action
212
- if should_add_payload[:lane] && Actions.lane_context[Actions::SharedValues::LANE_NAME]
213
- slack_attachment[:fields] << {
214
- title: 'Lane',
215
- value: Actions.lane_context[Actions::SharedValues::LANE_NAME],
216
- short: true
217
- }
218
- end
219
-
220
- # test_result
221
- if should_add_payload[:test_result]
222
- slack_attachment[:fields] << {
223
- title: 'Result',
224
- value: (options[:success] ? 'Success' : 'Error'),
225
- short: true
226
- }
227
- end
228
-
229
- # git branch
230
- if Actions.git_branch && should_add_payload[:git_branch]
231
- slack_attachment[:fields] << {
232
- title: 'Git Branch',
233
- value: Actions.git_branch,
234
- short: true
235
- }
236
- end
237
-
238
- # git_author
239
- if Actions.git_author_email && should_add_payload[:git_author]
240
- if FastlaneCore::Env.truthy?('FASTLANE_SLACK_HIDE_AUTHOR_ON_SUCCESS') && options[:success]
241
- # We only show the git author if the build failed
242
- else
243
- slack_attachment[:fields] << {
244
- title: 'Git Author',
245
- value: Actions.git_author_email,
246
- short: true
247
- }
248
- end
249
- end
250
-
251
- # last_git_commit
252
- if Actions.last_git_commit_message && should_add_payload[:last_git_commit]
253
- slack_attachment[:fields] << {
254
- title: 'Git Commit',
255
- value: Actions.last_git_commit_message,
256
- short: false
257
- }
258
- end
259
-
260
- # last_git_commit_hash
261
- if Actions.last_git_commit_hash(true) && should_add_payload[:last_git_commit_hash]
262
- slack_attachment[:fields] << {
263
- title: 'Git Commit Hash',
264
- value: Actions.last_git_commit_hash(short: true),
265
- short: false
266
- }
267
- end
268
-
269
- # merge additional properties
270
- deep_merge(slack_attachment, options[:attachment_properties])
296
+ def self.trim_message(message)
297
+ Runner.trim_message(message)
271
298
  end
272
299
 
273
- # Adapted from https://stackoverflow.com/a/30225093/158525
274
- def self.deep_merge(a, b)
275
- merger = proc do |key, v1, v2|
276
- Hash === v1 && Hash === v2 ?
277
- v1.merge(v2, &merger) : Array === v1 && Array === v2 ?
278
- v1 | v2 : [:undefined, nil, :nil].include?(v2) ? v1 : v2
279
- end
280
- a.merge(b, &merger)
300
+ def self.generate_slack_attachments(options)
301
+ UI.deprecated('`Fastlane::Actions::Slack.generate_slack_attachments` is subject to be removed as Slack recommends migrating `attachments` to Block Kit. fastlane will also follow the same direction.')
302
+ Runner.generate_slack_attachments(options)
281
303
  end
282
304
  end
283
305
  end