pantograph 0.1.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 (202) hide show
  1. checksums.yaml +7 -0
  2. data/.yardopts +1 -0
  3. data/LICENSE +21 -0
  4. data/README.md +197 -0
  5. data/bin/bin-proxy +19 -0
  6. data/bin/pantograph +23 -0
  7. data/pantograph/README.md +11 -0
  8. data/pantograph/lib/assets/ActionDetails.md.erb +106 -0
  9. data/pantograph/lib/assets/Actions.md.erb +43 -0
  10. data/pantograph/lib/assets/DefaultPantfileTemplate +20 -0
  11. data/pantograph/lib/assets/completions/completion.bash +23 -0
  12. data/pantograph/lib/assets/completions/completion.fish +39 -0
  13. data/pantograph/lib/assets/completions/completion.sh +12 -0
  14. data/pantograph/lib/assets/completions/completion.zsh +23 -0
  15. data/pantograph/lib/assets/custom_action_template.rb +80 -0
  16. data/pantograph/lib/assets/report_template.xml.erb +15 -0
  17. data/pantograph/lib/pantograph/action.rb +194 -0
  18. data/pantograph/lib/pantograph/action_collector.rb +35 -0
  19. data/pantograph/lib/pantograph/actions/README.md +3 -0
  20. data/pantograph/lib/pantograph/actions/actions_helper.rb +166 -0
  21. data/pantograph/lib/pantograph/actions/add_extra_platforms.rb +45 -0
  22. data/pantograph/lib/pantograph/actions/artifactory.rb +157 -0
  23. data/pantograph/lib/pantograph/actions/bundle_install.rb +156 -0
  24. data/pantograph/lib/pantograph/actions/changelog_from_git_commits.rb +197 -0
  25. data/pantograph/lib/pantograph/actions/clipboard.rb +52 -0
  26. data/pantograph/lib/pantograph/actions/cloc.rb +89 -0
  27. data/pantograph/lib/pantograph/actions/create_pull_request.rb +190 -0
  28. data/pantograph/lib/pantograph/actions/danger.rb +131 -0
  29. data/pantograph/lib/pantograph/actions/debug.rb +32 -0
  30. data/pantograph/lib/pantograph/actions/default_platform.rb +47 -0
  31. data/pantograph/lib/pantograph/actions/download.rb +76 -0
  32. data/pantograph/lib/pantograph/actions/echo.rb +14 -0
  33. data/pantograph/lib/pantograph/actions/ensure_bundle_exec.rb +59 -0
  34. data/pantograph/lib/pantograph/actions/ensure_env_vars.rb +58 -0
  35. data/pantograph/lib/pantograph/actions/ensure_git_branch.rb +69 -0
  36. data/pantograph/lib/pantograph/actions/ensure_git_status_clean.rb +81 -0
  37. data/pantograph/lib/pantograph/actions/erb.rb +88 -0
  38. data/pantograph/lib/pantograph/actions/get_build_number_repository.rb +120 -0
  39. data/pantograph/lib/pantograph/actions/get_github_release.rb +163 -0
  40. data/pantograph/lib/pantograph/actions/git_add.rb +93 -0
  41. data/pantograph/lib/pantograph/actions/git_branch.rb +58 -0
  42. data/pantograph/lib/pantograph/actions/git_commit.rb +80 -0
  43. data/pantograph/lib/pantograph/actions/git_pull.rb +53 -0
  44. data/pantograph/lib/pantograph/actions/git_submodule_update.rb +52 -0
  45. data/pantograph/lib/pantograph/actions/git_tag_exists.rb +74 -0
  46. data/pantograph/lib/pantograph/actions/github_api.rb +262 -0
  47. data/pantograph/lib/pantograph/actions/gradle.rb +278 -0
  48. data/pantograph/lib/pantograph/actions/import.rb +49 -0
  49. data/pantograph/lib/pantograph/actions/import_from_git.rb +71 -0
  50. data/pantograph/lib/pantograph/actions/is_ci.rb +51 -0
  51. data/pantograph/lib/pantograph/actions/jira.rb +115 -0
  52. data/pantograph/lib/pantograph/actions/lane_context.rb +60 -0
  53. data/pantograph/lib/pantograph/actions/last_git_commit.rb +58 -0
  54. data/pantograph/lib/pantograph/actions/last_git_tag.rb +51 -0
  55. data/pantograph/lib/pantograph/actions/make_changelog_from_jenkins.rb +81 -0
  56. data/pantograph/lib/pantograph/actions/min_pantograph_version.rb +57 -0
  57. data/pantograph/lib/pantograph/actions/nexus_upload.rb +230 -0
  58. data/pantograph/lib/pantograph/actions/notification.rb +75 -0
  59. data/pantograph/lib/pantograph/actions/number_of_commits.rb +75 -0
  60. data/pantograph/lib/pantograph/actions/opt_out_usage.rb +40 -0
  61. data/pantograph/lib/pantograph/actions/pantograph_version.rb +15 -0
  62. data/pantograph/lib/pantograph/actions/println.rb +14 -0
  63. data/pantograph/lib/pantograph/actions/prompt.rb +119 -0
  64. data/pantograph/lib/pantograph/actions/push_git_tags.rb +76 -0
  65. data/pantograph/lib/pantograph/actions/push_to_git_remote.rb +127 -0
  66. data/pantograph/lib/pantograph/actions/puts.rb +68 -0
  67. data/pantograph/lib/pantograph/actions/reset_git_repo.rb +121 -0
  68. data/pantograph/lib/pantograph/actions/rocket.rb +83 -0
  69. data/pantograph/lib/pantograph/actions/rsync.rb +74 -0
  70. data/pantograph/lib/pantograph/actions/ruby_version.rb +56 -0
  71. data/pantograph/lib/pantograph/actions/say.rb +56 -0
  72. data/pantograph/lib/pantograph/actions/scp.rb +114 -0
  73. data/pantograph/lib/pantograph/actions/set_github_release.rb +274 -0
  74. data/pantograph/lib/pantograph/actions/sh.rb +71 -0
  75. data/pantograph/lib/pantograph/actions/skip_docs.rb +52 -0
  76. data/pantograph/lib/pantograph/actions/slack.rb +288 -0
  77. data/pantograph/lib/pantograph/actions/sonar.rb +156 -0
  78. data/pantograph/lib/pantograph/actions/ssh.rb +162 -0
  79. data/pantograph/lib/pantograph/actions/twitter.rb +89 -0
  80. data/pantograph/lib/pantograph/actions/update_pantograph.rb +177 -0
  81. data/pantograph/lib/pantograph/actions/zip.rb +120 -0
  82. data/pantograph/lib/pantograph/auto_complete.rb +82 -0
  83. data/pantograph/lib/pantograph/boolean.rb +5 -0
  84. data/pantograph/lib/pantograph/cli_tools_distributor.rb +183 -0
  85. data/pantograph/lib/pantograph/command_line_handler.rb +43 -0
  86. data/pantograph/lib/pantograph/commands_generator.rb +344 -0
  87. data/pantograph/lib/pantograph/configuration_helper.rb +26 -0
  88. data/pantograph/lib/pantograph/core_ext/bundler_monkey_patch.rb +14 -0
  89. data/pantograph/lib/pantograph/documentation/actions_list.rb +214 -0
  90. data/pantograph/lib/pantograph/documentation/docs_generator.rb +95 -0
  91. data/pantograph/lib/pantograph/documentation/markdown_docs_generator.rb +221 -0
  92. data/pantograph/lib/pantograph/environment_printer.rb +282 -0
  93. data/pantograph/lib/pantograph/erb_template_helper.rb +30 -0
  94. data/pantograph/lib/pantograph/features.rb +4 -0
  95. data/pantograph/lib/pantograph/helper/README.md +29 -0
  96. data/pantograph/lib/pantograph/helper/dotenv_helper.rb +50 -0
  97. data/pantograph/lib/pantograph/helper/gem_helper.rb +26 -0
  98. data/pantograph/lib/pantograph/helper/git_helper.rb +135 -0
  99. data/pantograph/lib/pantograph/helper/gradle_helper.rb +62 -0
  100. data/pantograph/lib/pantograph/helper/sh_helper.rb +134 -0
  101. data/pantograph/lib/pantograph/junit_generator.rb +27 -0
  102. data/pantograph/lib/pantograph/lane.rb +97 -0
  103. data/pantograph/lib/pantograph/lane_list.rb +77 -0
  104. data/pantograph/lib/pantograph/lane_manager.rb +140 -0
  105. data/pantograph/lib/pantograph/lane_manager_base.rb +92 -0
  106. data/pantograph/lib/pantograph/markdown_table_formatter.rb +62 -0
  107. data/pantograph/lib/pantograph/new_action.rb +47 -0
  108. data/pantograph/lib/pantograph/one_off.rb +45 -0
  109. data/pantograph/lib/pantograph/other_action.rb +29 -0
  110. data/pantograph/lib/pantograph/pant_file.rb +377 -0
  111. data/pantograph/lib/pantograph/pantograph_require.rb +75 -0
  112. data/pantograph/lib/pantograph/plugins/plugin_fetcher.rb +55 -0
  113. data/pantograph/lib/pantograph/plugins/plugin_generator.rb +86 -0
  114. data/pantograph/lib/pantograph/plugins/plugin_generator_ui.rb +19 -0
  115. data/pantograph/lib/pantograph/plugins/plugin_info.rb +49 -0
  116. data/pantograph/lib/pantograph/plugins/plugin_info_collector.rb +159 -0
  117. data/pantograph/lib/pantograph/plugins/plugin_manager.rb +387 -0
  118. data/pantograph/lib/pantograph/plugins/plugin_search.rb +46 -0
  119. data/pantograph/lib/pantograph/plugins/plugin_update_manager.rb +70 -0
  120. data/pantograph/lib/pantograph/plugins/plugins.rb +12 -0
  121. data/pantograph/lib/pantograph/plugins/template/%gem_name%.gemspec.erb +35 -0
  122. data/pantograph/lib/pantograph/plugins/template/.circleci/config.yml +43 -0
  123. data/pantograph/lib/pantograph/plugins/template/.gitignore +12 -0
  124. data/pantograph/lib/pantograph/plugins/template/.rspec +5 -0
  125. data/pantograph/lib/pantograph/plugins/template/.rubocop.yml +179 -0
  126. data/pantograph/lib/pantograph/plugins/template/.travis.yml +4 -0
  127. data/pantograph/lib/pantograph/plugins/template/Gemfile +6 -0
  128. data/pantograph/lib/pantograph/plugins/template/LICENSE.erb +21 -0
  129. data/pantograph/lib/pantograph/plugins/template/README.md.erb +52 -0
  130. data/pantograph/lib/pantograph/plugins/template/Rakefile +9 -0
  131. data/pantograph/lib/pantograph/plugins/template/lib/pantograph/plugin/%plugin_name%/actions/%plugin_name%_action.rb.erb +47 -0
  132. data/pantograph/lib/pantograph/plugins/template/lib/pantograph/plugin/%plugin_name%/helper/%plugin_name%_helper.rb.erb +16 -0
  133. data/pantograph/lib/pantograph/plugins/template/lib/pantograph/plugin/%plugin_name%/version.rb.erb +5 -0
  134. data/pantograph/lib/pantograph/plugins/template/lib/pantograph/plugin/%plugin_name%.rb.erb +16 -0
  135. data/pantograph/lib/pantograph/plugins/template/pantograph/Pantfile.erb +3 -0
  136. data/pantograph/lib/pantograph/plugins/template/pantograph/Pluginfile.erb +1 -0
  137. data/pantograph/lib/pantograph/plugins/template/spec/%plugin_name%_action_spec.rb.erb +9 -0
  138. data/pantograph/lib/pantograph/plugins/template/spec/spec_helper.rb.erb +15 -0
  139. data/pantograph/lib/pantograph/runner.rb +371 -0
  140. data/pantograph/lib/pantograph/server/action_command.rb +61 -0
  141. data/pantograph/lib/pantograph/server/action_command_return.rb +14 -0
  142. data/pantograph/lib/pantograph/server/command_executor.rb +7 -0
  143. data/pantograph/lib/pantograph/server/command_parser.rb +36 -0
  144. data/pantograph/lib/pantograph/server/control_command.rb +23 -0
  145. data/pantograph/lib/pantograph/server/json_return_value_processor.rb +72 -0
  146. data/pantograph/lib/pantograph/server/socket_server.rb +232 -0
  147. data/pantograph/lib/pantograph/server/socket_server_action_command_executor.rb +101 -0
  148. data/pantograph/lib/pantograph/setup/setup.rb +290 -0
  149. data/pantograph/lib/pantograph/setup/setup_android.rb +64 -0
  150. data/pantograph/lib/pantograph/setup/setup_ios.rb +412 -0
  151. data/pantograph/lib/pantograph/shells.rb +6 -0
  152. data/pantograph/lib/pantograph/supported_platforms.rb +28 -0
  153. data/pantograph/lib/pantograph/tools.rb +10 -0
  154. data/pantograph/lib/pantograph/version.rb +5 -0
  155. data/pantograph/lib/pantograph.rb +51 -0
  156. data/pantograph_core/README.md +79 -0
  157. data/pantograph_core/lib/assets/XMLTemplate.xml.erb +12 -0
  158. data/pantograph_core/lib/pantograph_core/analytics/action_completion_context.rb +34 -0
  159. data/pantograph_core/lib/pantograph_core/analytics/action_launch_context.rb +38 -0
  160. data/pantograph_core/lib/pantograph_core/analytics/analytics_event_builder.rb +23 -0
  161. data/pantograph_core/lib/pantograph_core/analytics/analytics_ingester_client.rb +54 -0
  162. data/pantograph_core/lib/pantograph_core/analytics/analytics_session.rb +71 -0
  163. data/pantograph_core/lib/pantograph_core/cert_checker.rb +116 -0
  164. data/pantograph_core/lib/pantograph_core/command_executor.rb +99 -0
  165. data/pantograph_core/lib/pantograph_core/configuration/commander_generator.rb +103 -0
  166. data/pantograph_core/lib/pantograph_core/configuration/config_item.rb +314 -0
  167. data/pantograph_core/lib/pantograph_core/configuration/configuration.rb +332 -0
  168. data/pantograph_core/lib/pantograph_core/configuration/configuration_file.rb +182 -0
  169. data/pantograph_core/lib/pantograph_core/core_ext/shellwords.rb +63 -0
  170. data/pantograph_core/lib/pantograph_core/core_ext/string.rb +17 -0
  171. data/pantograph_core/lib/pantograph_core/env.rb +9 -0
  172. data/pantograph_core/lib/pantograph_core/feature/feature.rb +51 -0
  173. data/pantograph_core/lib/pantograph_core/features.rb +4 -0
  174. data/pantograph_core/lib/pantograph_core/globals.rb +27 -0
  175. data/pantograph_core/lib/pantograph_core/helper.rb +409 -0
  176. data/pantograph_core/lib/pantograph_core/keychain_importer.rb +74 -0
  177. data/pantograph_core/lib/pantograph_core/languages.rb +14 -0
  178. data/pantograph_core/lib/pantograph_core/module.rb +29 -0
  179. data/pantograph_core/lib/pantograph_core/pantograph_folder.rb +39 -0
  180. data/pantograph_core/lib/pantograph_core/pantograph_pty.rb +57 -0
  181. data/pantograph_core/lib/pantograph_core/pkg_file_analyser.rb +44 -0
  182. data/pantograph_core/lib/pantograph_core/print_table.rb +131 -0
  183. data/pantograph_core/lib/pantograph_core/string_filters.rb +51 -0
  184. data/pantograph_core/lib/pantograph_core/swag.rb +85 -0
  185. data/pantograph_core/lib/pantograph_core/tag_version.rb +31 -0
  186. data/pantograph_core/lib/pantograph_core/test_parser.rb +107 -0
  187. data/pantograph_core/lib/pantograph_core/ui/disable_colors.rb +17 -0
  188. data/pantograph_core/lib/pantograph_core/ui/errors/pantograph_common_error.rb +19 -0
  189. data/pantograph_core/lib/pantograph_core/ui/errors/pantograph_crash.rb +11 -0
  190. data/pantograph_core/lib/pantograph_core/ui/errors/pantograph_error.rb +25 -0
  191. data/pantograph_core/lib/pantograph_core/ui/errors/pantograph_exception.rb +19 -0
  192. data/pantograph_core/lib/pantograph_core/ui/errors/pantograph_shell_error.rb +11 -0
  193. data/pantograph_core/lib/pantograph_core/ui/errors.rb +1 -0
  194. data/pantograph_core/lib/pantograph_core/ui/github_issue_inspector_reporter.rb +62 -0
  195. data/pantograph_core/lib/pantograph_core/ui/implementations/shell.rb +159 -0
  196. data/pantograph_core/lib/pantograph_core/ui/interface.rb +205 -0
  197. data/pantograph_core/lib/pantograph_core/ui/pantograph_runner.rb +276 -0
  198. data/pantograph_core/lib/pantograph_core/ui/ui.rb +26 -0
  199. data/pantograph_core/lib/pantograph_core/update_checker/changelog.rb +37 -0
  200. data/pantograph_core/lib/pantograph_core/update_checker/update_checker.rb +107 -0
  201. data/pantograph_core/lib/pantograph_core.rb +45 -0
  202. metadata +987 -0
@@ -0,0 +1,232 @@
1
+ require 'pantograph/server/command_executor.rb'
2
+ require 'pantograph/server/command_parser.rb'
3
+ require 'pantograph/server/json_return_value_processor.rb'
4
+ require 'socket'
5
+ require 'json'
6
+
7
+ module Pantograph
8
+ class SocketServer
9
+ COMMAND_EXECUTION_STATE = {
10
+ ready: :ready,
11
+ already_shutdown: :already_shutdown,
12
+ error: :error
13
+ }
14
+
15
+ attr_accessor :command_executor
16
+ attr_accessor :return_value_processor
17
+
18
+ def initialize(
19
+ command_executor: nil,
20
+ return_value_processor: nil,
21
+ connection_timeout: 5,
22
+ stay_alive: false,
23
+ port: 2000
24
+ )
25
+ if return_value_processor.nil?
26
+ return_value_processor = JSONReturnValueProcessor.new
27
+ end
28
+
29
+ @command_executor = command_executor
30
+ @return_value_processor = return_value_processor
31
+ @connection_timeout = connection_timeout.to_i
32
+ @stay_alive = stay_alive
33
+ @port = port.to_i
34
+ end
35
+
36
+ # this is the public API, don't call anything else
37
+ def start
38
+ listen
39
+
40
+ while @stay_alive
41
+ UI.important("stay_alive is set to true, restarting server")
42
+ listen
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def receive_and_process_commands
49
+ loop do # no idea how many commands are coming, so we loop until an error or the done command is sent
50
+ execution_state = COMMAND_EXECUTION_STATE[:ready]
51
+
52
+ command_string = nil
53
+ begin
54
+ command_string = @client.recv(1_048_576) # 1024 * 1024
55
+ rescue Errno::ECONNRESET => e
56
+ UI.verbose(e)
57
+ execution_state = COMMAND_EXECUTION_STATE[:error]
58
+ end
59
+
60
+ if execution_state == COMMAND_EXECUTION_STATE[:ready]
61
+ # Ok, all is good, let's see what command we have
62
+ execution_state = parse_and_execute_command(command_string: command_string)
63
+ end
64
+
65
+ case execution_state
66
+ when COMMAND_EXECUTION_STATE[:ready]
67
+ # command executed successfully, let's setup for the next command
68
+ next
69
+ when COMMAND_EXECUTION_STATE[:already_shutdown]
70
+ # we shutdown in response to a command, nothing left to do but exit
71
+ break
72
+ when COMMAND_EXECUTION_STATE[:error]
73
+ # we got an error somewhere, let's shutdown and exit
74
+ handle_disconnect(error: true, exit_reason: :error)
75
+ break
76
+ end
77
+ end
78
+ end
79
+
80
+ def parse_and_execute_command(command_string: nil)
81
+ command = CommandParser.parse(json: command_string)
82
+ case command
83
+ when ControlCommand
84
+ return handle_control_command(command)
85
+ when ActionCommand
86
+ return handle_action_command(command)
87
+ end
88
+
89
+ # catch all
90
+ raise "Command #{command} not supported"
91
+ end
92
+
93
+ # we got a server control command from the client to do something like shutdown
94
+ def handle_control_command(command)
95
+ exit_reason = nil
96
+ if command.cancel_signal?
97
+ UI.verbose("received cancel signal shutting down, reason: #{command.reason}")
98
+
99
+ # send an ack to the client to let it know we're shutting down
100
+ cancel_response = '{"payload":{"status":"cancelled"}}'
101
+ send_response(cancel_response)
102
+
103
+ exit_reason = :cancelled
104
+ elsif command.done_signal?
105
+ UI.verbose("received done signal shutting down")
106
+
107
+ # client is already in the process of shutting down, no need to ack
108
+ exit_reason = :done
109
+ end
110
+
111
+ # if the command came in with a user-facing message, display it
112
+ if command.user_message
113
+ UI.important(command.user_message)
114
+ end
115
+
116
+ # currently all control commands should trigger a disconnect and shutdown
117
+ handle_disconnect(error: false, exit_reason: exit_reason)
118
+ return COMMAND_EXECUTION_STATE[:already_shutdown]
119
+ end
120
+
121
+ # execute and send back response to client
122
+ def handle_action_command(command)
123
+ response_json = process_action_command(command: command)
124
+ return send_response(response_json)
125
+ end
126
+
127
+ # send json back to client
128
+ def send_response(json)
129
+ UI.verbose("sending #{json}")
130
+ begin
131
+ @client.puts(json) # Send some json to the client
132
+ rescue Errno::EPIPE => e
133
+ UI.verbose(e)
134
+ return COMMAND_EXECUTION_STATE[:error]
135
+ end
136
+ return COMMAND_EXECUTION_STATE[:ready]
137
+ end
138
+
139
+ def listen
140
+ @server = TCPServer.open('localhost', @port) # Socket to listen on port 2000
141
+ UI.verbose("Waiting for #{@connection_timeout} seconds for a connection from PantographRunner")
142
+
143
+ # set thread local to ready so we can check it
144
+ Thread.current[:ready] = true
145
+ @client = nil
146
+ begin
147
+ Timeout.timeout(@connection_timeout) do
148
+ @client = @server.accept # Wait for a client to connect
149
+ end
150
+ rescue Timeout::Error
151
+ UI.user_error!("pantograph failed to receive a connection from the PantographRunner binary after #{@connection_timeout} seconds, shutting down")
152
+ rescue StandardError => e
153
+ UI.user_error!("Something went wrong while waiting for a connection from the PantographRunner binary, shutting down\n#{e}")
154
+ end
155
+ UI.verbose("Client connected")
156
+
157
+ # this loops forever
158
+ receive_and_process_commands
159
+ end
160
+
161
+ def handle_disconnect(error: false, exit_reason: :error)
162
+ Thread.current[:exit_reason] = exit_reason
163
+
164
+ UI.important("Client disconnected, a pipe broke, or received malformed data") if exit_reason == :error
165
+ # clean up
166
+ @client.close
167
+ @client = nil
168
+
169
+ @server.close
170
+ @server = nil
171
+ end
172
+
173
+ # record pantograph action command and then execute it
174
+ def process_action_command(command: nil)
175
+ UI.verbose("received command:#{command.inspect}")
176
+ return execute_action_command(command: command)
177
+ end
178
+
179
+ # execute pantograph action command
180
+ def execute_action_command(command: nil)
181
+ command_return = @command_executor.execute(command: command, target_object: nil)
182
+ ## probably need to just return Strings, or ready_for_next with object isn't String
183
+ return_object = command_return.return_value
184
+ return_value_type = command_return.return_value_type
185
+ closure_arg = command_return.closure_argument_value
186
+
187
+ return_object = return_value_processor.prepare_object(
188
+ return_value: return_object,
189
+ return_value_type: return_value_type
190
+ )
191
+
192
+ if closure_arg.nil?
193
+ closure_arg = closure_arg.to_s
194
+ else
195
+ closure_arg = return_value_processor.prepare_object(
196
+ return_value: closure_arg,
197
+ return_value_type: :string # always assume string for closure error_callback
198
+ )
199
+ end
200
+
201
+ Thread.current[:exception] = nil
202
+
203
+ payload = {
204
+ payload: {
205
+ status: "ready_for_next",
206
+ return_object: return_object,
207
+ closure_argument_value: closure_arg
208
+ }
209
+ }
210
+ return JSON.generate(payload)
211
+ rescue StandardError => e
212
+ Thread.current[:exception] = e
213
+
214
+ exception_array = []
215
+ exception_array << "#{e.class}:"
216
+ exception_array << e.backtrace
217
+
218
+ while e.respond_to?("cause") && (e = e.cause)
219
+ exception_array << "cause: #{e.class}"
220
+ exception_array << e.backtrace
221
+ end
222
+
223
+ payload = {
224
+ payload: {
225
+ status: "failure",
226
+ failure_information: exception_array.flatten
227
+ }
228
+ }
229
+ return JSON.generate(payload)
230
+ end
231
+ end
232
+ end
@@ -0,0 +1,101 @@
1
+ require 'pantograph/server/action_command_return.rb'
2
+ require 'pantograph/server/command_parser.rb'
3
+ require 'pantograph/server/command_executor.rb'
4
+
5
+ module Pantograph
6
+ # Handles receiving commands from the socket server, finding the Action to be invoked,
7
+ # invoking it, and returning any return values
8
+ class SocketServerActionCommandExecutor < CommandExecutor
9
+ attr_accessor :runner
10
+ attr_accessor :actions_requiring_special_handling
11
+
12
+ def initialize
13
+ Pantograph.load_actions
14
+ @runner = Runner.new
15
+ @actions_requiring_special_handling = ["sh"].to_set
16
+ end
17
+
18
+ def execute(command: nil, target_object: nil)
19
+ action_name = command.method_name
20
+ action_class_ref = class_ref_for_action(named: action_name)
21
+ parameter_map = {}
22
+ closure_argument_value = nil
23
+
24
+ command.args.each do |arg|
25
+ arg_value = arg.value
26
+ if arg.value_type.to_s.to_sym == :string_closure
27
+ closure = proc { |string_value| closure_argument_value = string_value }
28
+ arg_value = closure
29
+ end
30
+ parameter_map[arg.name.to_sym] = arg_value
31
+ end
32
+
33
+ if @actions_requiring_special_handling.include?(action_name)
34
+ command_return = run_action_requiring_special_handling(
35
+ command: command,
36
+ parameter_map: parameter_map,
37
+ action_return_type: action_class_ref.return_type
38
+ )
39
+ return command_return
40
+ end
41
+
42
+ action_return = run(
43
+ action_named: action_name,
44
+ action_class_ref: action_class_ref,
45
+ parameter_map: parameter_map
46
+ )
47
+
48
+ command_return = ActionCommandReturn.new(
49
+ return_value: action_return,
50
+ return_value_type: action_class_ref.return_type,
51
+ closure_argument_value: closure_argument_value
52
+ )
53
+ return command_return
54
+ end
55
+
56
+ def class_ref_for_action(named: nil)
57
+ class_ref = Actions.action_class_ref(named)
58
+ unless class_ref
59
+ if Pantograph::Actions.formerly_bundled_actions.include?(action)
60
+ # This was a formerly bundled action which is now a plugin.
61
+ UI.verbose(caller.join("\n"))
62
+ UI.user_error!("The action '#{action}' is no longer bundled with pantograph. You can install it using `pantograph add_plugin #{action}`")
63
+ else
64
+ Pantograph::ActionsList.print_suggestions(action)
65
+ UI.user_error!("Action '#{action}' not available, run `pantograph actions` to get a full list")
66
+ end
67
+ end
68
+
69
+ return class_ref
70
+ end
71
+
72
+ def run(action_named: nil, action_class_ref: nil, parameter_map: nil)
73
+ action_return = runner.execute_action(action_named, action_class_ref, [parameter_map], custom_dir: '.')
74
+ return action_return
75
+ end
76
+
77
+ # Some actions have special handling in pant_file.rb, that means we can't directly call the action
78
+ # but we have to use the same logic that is in pant_file.rb instead.
79
+ # That's where this switch statement comes into play
80
+ def run_action_requiring_special_handling(command: nil, parameter_map: nil, action_return_type: nil)
81
+ action_return = nil
82
+ closure_argument_value = nil # only used if the action uses it
83
+
84
+ case command.method_name
85
+ when "sh"
86
+ error_callback = proc { |string_value| closure_argument_value = string_value }
87
+ command_param = parameter_map[:command]
88
+ log_param = parameter_map[:log]
89
+ action_return = Pantograph::PantFile.sh(command_param, log: log_param, error_callback: error_callback)
90
+ end
91
+
92
+ command_return = ActionCommandReturn.new(
93
+ return_value: action_return,
94
+ return_value_type: action_return_type,
95
+ closure_argument_value: closure_argument_value
96
+ )
97
+
98
+ return command_return
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,290 @@
1
+ require "tty-spinner"
2
+
3
+ module Pantograph
4
+ class Setup
5
+ # :ios or :android
6
+ attr_accessor :platform
7
+
8
+ # Path to the xcodeproj or xcworkspace
9
+ attr_accessor :project_path
10
+
11
+ # Used for :manual sometimes
12
+ attr_accessor :preferred_setup_method
13
+
14
+ # remember if there were multiple projects
15
+ # if so, we set it as part of the Pantfile
16
+ attr_accessor :had_multiple_projects_to_choose_from
17
+
18
+ # The current content of the generated Pantfile
19
+ attr_accessor :pantfile_content
20
+
21
+ # For iOS projects that's the Apple ID email
22
+ attr_accessor :user
23
+
24
+ # This is the lane that we tell the user to run to try the new pantograph setup
25
+ # This needs to be setup by each setup
26
+ attr_accessor :lane_to_mention
27
+
28
+ # Start the setup process
29
+ # rubocop:disable Metrics/BlockNesting
30
+ def self.start(user: nil)
31
+ if PantographCore::PantographFolder.setup? && !Helper.test?
32
+ require 'pantograph/lane_list'
33
+ Pantograph::LaneList.output(PantographCore::PantographFolder.pantfile_path)
34
+ UI.important("------------------")
35
+ UI.important("pantograph is already set up at path `#{PantographCore::PantographFolder.path}`, see the available lanes above")
36
+ UI.message("")
37
+
38
+ setup_ios = self.new
39
+ setup_ios.add_or_update_gemfile(update_gemfile_if_needed: false)
40
+ setup_ios.suggest_next_steps
41
+ return
42
+ end
43
+
44
+ # this is used by e.g. configuration.rb to not show warnings when running produce
45
+ ENV["PANTOGRAPH_ONBOARDING_IN_PROCESS"] = 1.to_s
46
+
47
+ spinner = TTY::Spinner.new("[:spinner] Looking for iOS and Android projects in current directory...", format: :dots)
48
+ spinner.auto_spin
49
+
50
+ maven_projects = Dir["**/pom.xml"]
51
+ gradle_projects = Dir["**/*.gradle"] + Dir["**/*.gradle.kts"]
52
+
53
+ spinner.success
54
+
55
+ PantographCore::PantographFolder.create_folder!
56
+
57
+ # Currently we prefer iOS app projects, as the `init` process is
58
+ # more intelligent and does more things. The user can easily add
59
+ # the `:android` platform to the resulting Pantfile
60
+ if maven_projects.count > 0
61
+ current_directory = maven_projects.find_all do |current_project_path|
62
+ current_project_path.split(File::Separator).count == 1
63
+ end
64
+ chosen_project = nil
65
+ had_multiple_projects_to_choose_from = false
66
+
67
+ if current_directory.count == 1
68
+ chosen_project = current_directory.first
69
+ elsif current_directory.count > 1
70
+ if current_directory.count == 2
71
+ # This is a common case (e.g. with CocoaPods), where the project has an xcodeproj and an xcworkspace file
72
+ extensions = [File.extname(current_directory[0]), File.extname(current_directory[1])]
73
+ if extensions.sort == [".xcodeproj", ".xcworkspace"].sort
74
+ # Yep, that's this kind of setup
75
+ chosen_project = current_directory.find { |d| d.end_with?(".xcworkspace") }
76
+ end
77
+ end
78
+ chosen_project ||= UI.select("Multiple iOS projects found in current directory", current_directory)
79
+ had_multiple_projects_to_choose_from = true
80
+ else
81
+ UI.error("It looks like there is no iOS project in the current directory, though we did find one in a sub-directory")
82
+ UI.error("Please `cd` into the directory of the intended Xcode project you wish to use.")
83
+ UI.user_error!("Please `cd` into the directory of the intended Xcode project you wish to use and run `pantograph init` again")
84
+ end
85
+
86
+ UI.message("Detected an iOS/macOS project in the current directory: '#{chosen_project}'")
87
+
88
+ SetupIos.new(
89
+ user: user,
90
+ project_path: chosen_project,
91
+ had_multiple_projects_to_choose_from: had_multiple_projects_to_choose_from
92
+ ).setup_ios
93
+ elsif gradle_projects.count > 0
94
+ UI.message("Detected an Android project in the current directory...")
95
+ SetupAndroid.new.setup_android
96
+ else
97
+ UI.error("No iOS or Android projects were found in directory '#{Dir.pwd}'")
98
+ UI.error("Make sure to `cd` into the directory containing your iOS or Android app")
99
+ if UI.confirm("Alternatively, would you like to manually setup a pantograph config in the current directory instead?")
100
+ SetupIos.new(
101
+ user: user,
102
+ project_path: chosen_project,
103
+ had_multiple_projects_to_choose_from: had_multiple_projects_to_choose_from,
104
+ preferred_setup_method: :ios_manual
105
+ ).setup_ios
106
+ else
107
+ UI.user_error!("Make sure to `cd` into the directory containing your project and then use `pantograph init` again")
108
+ end
109
+ end
110
+ end
111
+ # rubocop:enable Metrics/BlockNesting
112
+
113
+ def initialize(user: nil, project_path: nil, had_multiple_projects_to_choose_from: nil, preferred_setup_method: nil)
114
+ self.user = user
115
+ self.project_path = project_path
116
+ self.had_multiple_projects_to_choose_from = had_multiple_projects_to_choose_from
117
+ self.preferred_setup_method = preferred_setup_method
118
+ end
119
+
120
+ # Helpers
121
+ def welcome_to_pantograph
122
+ UI.header("Welcome to pantograph 🚀")
123
+ UI.message("pantograph can help you with all kinds of automation")
124
+ UI.message("We recommend automating one task first, and then gradually automating more over time")
125
+ end
126
+
127
+ # Append a lane to the current Pantfile template we're generating
128
+ def append_lane(lane)
129
+ lane.compact! # remove nil values
130
+
131
+ new_lines = "\n\n"
132
+ new_lines = "" unless self.pantfile_content.include?("lane :") # the first lane we don't want new lines
133
+
134
+ self.pantfile_content.gsub!("[[LANES]]", "#{new_lines} #{lane.join("\n ")}[[LANES]]")
135
+ end
136
+
137
+ def write_pantfile!
138
+ # Write the Pantfile
139
+ pantfile_file_name = "Pantfile"
140
+
141
+ pantfile_path = File.join(PantographCore::PantographFolder.path, pantfile_file_name)
142
+ self.pantfile_content.gsub!("[[LANES]]", "") # since we always keep it until writing out
143
+ File.write(pantfile_path, self.pantfile_content) # remove trailing spaces before platform ends
144
+
145
+ add_or_update_gemfile(update_gemfile_if_needed: true)
146
+
147
+ UI.header("✅ Successfully generated pantograph configuration")
148
+ UI.message("Generated Pantfile at path `#{pantfile_path}`")
149
+ UI.message("Gemfile and Gemfile.lock at path `#{gemfile_path}`")
150
+
151
+ UI.message("Please check the newly generated configuration files into git along with your project")
152
+ UI.message("This way everyone in your team can benefit from your pantograph setup")
153
+ continue_with_enter
154
+ end
155
+
156
+ def gemfile_path
157
+ "Gemfile"
158
+ end
159
+
160
+ # Gemfile related code:
161
+ def gemfile_exists?
162
+ return File.exist?(gemfile_path)
163
+ end
164
+
165
+ def setup_gemfile!
166
+ # No Gemfile yet
167
+ gemfile_content = []
168
+ gemfile_content << "source \"https://rubygems.org\""
169
+ gemfile_content << ""
170
+ gemfile_content << 'gem "pantograph"'
171
+ gemfile_content << ""
172
+ File.write(gemfile_path, gemfile_content.join("\n"))
173
+
174
+ UI.message("Installing dependencies for you...")
175
+ PantographCore::CommandExecutor.execute(
176
+ command: "bundle update",
177
+ print_all: PantographCore::Globals.verbose?,
178
+ print_command: true,
179
+ error: proc do |error_output|
180
+ UI.error("Something went wrong when running `bundle update` for you")
181
+ UI.error("Please take a look at your Gemfile at path `#{gemfile_path}`")
182
+ UI.error("and make sure you can run `bundle update` on your machine.")
183
+ end
184
+ )
185
+ end
186
+
187
+ def ensure_gemfile_valid!(update_gemfile_if_needed: false)
188
+ gemfile_content = File.read(gemfile_path)
189
+ unless gemfile_content.include?("https://rubygems.org")
190
+ UI.error("You have a local Gemfile, but RubyGems isn't defined as source")
191
+ UI.error("Please update your Gemfile at path `#{gemfile_path}` to include")
192
+ UI.important("")
193
+ UI.important("source \"https://rubygems.org\"")
194
+ UI.important("")
195
+ UI.error("Update your Gemfile, and run `bundle update` afterwards")
196
+ end
197
+
198
+ unless gemfile_content.include?("pantograph")
199
+ if update_gemfile_if_needed
200
+ gemfile_content << "\n\ngem \"pantograph\""
201
+ UI.message("Adding `pantograph` to your existing Gemfile at path '#{gemfile_path}'")
202
+
203
+ File.write(gemfile_path, gemfile_content)
204
+ else
205
+ UI.error("You have a local Gemfile, but it doesn't include \"pantograph\" as a dependency")
206
+ UI.error("Please add `gem \"pantograph\"` to your Gemfile")
207
+ end
208
+ end
209
+ end
210
+
211
+ # This method is responsible for ensuring there is a working
212
+ # Gemfile, and that `pantograph` is defined as a dependency
213
+ # while also having `rubygems` as a gem source
214
+ def add_or_update_gemfile(update_gemfile_if_needed: false)
215
+ if gemfile_exists?
216
+ ensure_gemfile_valid!(update_gemfile_if_needed: update_gemfile_if_needed)
217
+ else
218
+ if update_gemfile_if_needed || UI.confirm("It is recommended to run pantograph with a Gemfile set up, do you want pantograph to create one for you?")
219
+ setup_gemfile!
220
+ end
221
+ end
222
+ return gemfile_path
223
+ end
224
+
225
+ def finish_up
226
+ write_pantfile!
227
+ show_analytics_note
228
+ explain_concepts
229
+ suggest_next_steps
230
+ end
231
+
232
+ def pantfile_template_content
233
+ path = "#{Pantograph::ROOT}/lib/assets/DefaultPantfileTemplate"
234
+ return File.read(path)
235
+ end
236
+
237
+ def explain_concepts
238
+ UI.header("pantograph lanes")
239
+ UI.message("pantograph uses a " + "`Pantfile`".yellow + " to store the automation configuration")
240
+ UI.message("Within that, you'll see different " + "lanes".yellow + ".")
241
+ UI.message("Each is there to automate a different task, like testing, building, or pushing new releases")
242
+ continue_with_enter
243
+
244
+ UI.header("How to customize your Pantfile")
245
+ UI.message("Use a text editor of your choice to open the newly created Pantfile and take a look")
246
+ UI.message("You can now edit the available lanes and actions to customize the setup to fit your needs")
247
+ UI.message("To get a list of all the available actions, open " + "https://johnknapprs.github.io/pantograph/".cyan)
248
+ continue_with_enter
249
+ end
250
+
251
+ def continue_with_enter
252
+ UI.input("Continue by pressing Enter ⏎")
253
+ end
254
+
255
+ def suggest_next_steps
256
+ UI.header("Where to go from here?")
257
+ if self.platform == :android
258
+ UI.message("📸 Learn more about how to automatically generate localized Google Play screenshots:")
259
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/android/screenshots/".cyan)
260
+ UI.message("👩‍✈️ Learn more about distribution to beta testing services:")
261
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/android/beta-deployment/".cyan)
262
+ UI.message("🚀 Learn more about how to automate the Google Play release process:")
263
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/android/release-deployment/".cyan)
264
+ else
265
+ UI.message("📸 Learn more about how to automatically generate localized App Store screenshots:")
266
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/ios/screenshots/".cyan)
267
+ UI.message("👩‍✈️ Learn more about distribution to beta testing services:")
268
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/ios/beta-deployment/".cyan)
269
+ UI.message("🚀 Learn more about how to automate the App Store release process:")
270
+ UI.message("\t\thttps://docs.pantograph.tools/getting-started/ios/appstore-deployment/".cyan)
271
+ UI.message("👩‍⚕️ Learn more about how to setup code signing with pantograph")
272
+ UI.message("\t\thttps://docs.pantograph.tools/codesigning/getting-started/".cyan)
273
+ end
274
+
275
+ # we crash here, so that this never happens when a new setup method is added
276
+ return if self.lane_to_mention.to_s.length == 0
277
+ UI.message("")
278
+ UI.message("To try your new pantograph setup, just enter and run")
279
+ UI.command("pantograph #{self.lane_to_mention}")
280
+ end
281
+
282
+ def show_analytics_note
283
+ UI.message("pantograph will collect the number of errors for each action to detect integration issues")
284
+ UI.message("No sensitive/private information will be uploaded, more information: " + "https://docs.pantograph.tools/#metrics".cyan)
285
+ end
286
+ end
287
+ end
288
+
289
+ require 'pantograph/setup/setup_ios'
290
+ require 'pantograph/setup/setup_android'
@@ -0,0 +1,64 @@
1
+ module Pantograph
2
+ class SetupAndroid < Setup
3
+ attr_accessor :json_key_file
4
+ attr_accessor :package_name
5
+
6
+ def setup_android
7
+ self.platform = :android
8
+
9
+ welcome_to_pantograph
10
+
11
+ self.pantfile_content = pantfile_template_content
12
+
13
+ fetch_information_for_appfile
14
+
15
+ PantographCore::PantographFolder.create_folder!
16
+
17
+ self.append_lane([
18
+ "desc \"Runs all the tests\"",
19
+ "lane :test do",
20
+ " gradle(task: \"test\")",
21
+ "end"
22
+ ])
23
+
24
+ self.append_lane([
25
+ "desc \"Submit a new Beta Build to Crashlytics Beta\"",
26
+ "lane :beta do",
27
+ " gradle(task: \"clean assembleRelease\")",
28
+ " crashlytics",
29
+ "",
30
+ " # sh \"your_script.sh\"",
31
+ " # You can also use other beta testing services here",
32
+ "end"
33
+ ])
34
+
35
+ self.append_lane([
36
+ "desc \"Deploy a new version to the Google Play\"",
37
+ "lane :deploy do",
38
+ " gradle(task: \"clean assembleRelease\")",
39
+ " upload_to_play_store",
40
+ "end"
41
+ ])
42
+
43
+ self.lane_to_mention = "test"
44
+
45
+ finish_up
46
+ end
47
+
48
+ def fetch_information_for_appfile
49
+ UI.message('')
50
+ UI.message("To avoid re-entering your package name and issuer every time you run pantograph, we'll store those in a so-called Appfile.")
51
+
52
+ self.package_name = UI.input("Package Name (com.krausefx.app): ")
53
+ puts("")
54
+ puts("To automatically upload builds and metadata to Google Play, pantograph needs a service account json secret file".yellow)
55
+ puts("Feel free to press Enter at any time in order to skip providing pieces of information when asked")
56
+ end
57
+
58
+ def finish_up
59
+ self.pantfile_content.gsub!(":ios", ":android")
60
+
61
+ super
62
+ end
63
+ end
64
+ end