fastlane 2.178.0 → 2.182.0

Sign up to get free protection for your applications and to get access to all the features.
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