panacea-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/exe/panacea ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ lib = File.expand_path("../lib", __dir__)
5
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
+
7
+ require "panacea/rails"
8
+
9
+ Panacea::Rails.init
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "slop"
4
+ require "panacea/rails/version"
5
+ require "panacea/rails/runner"
6
+
7
+ module Panacea # :nodoc:
8
+ ###
9
+ # == Panacea::Rails
10
+ #
11
+ # This module is the access point to the configuration questions and the Panacea::Rails::Generator.
12
+ module Rails
13
+ class << self
14
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/LineLength, Metrics/BlockLength
15
+
16
+ ###
17
+ # This method uses Slop to handle the CLI command and its arguments.
18
+ # It accepts most of the default Rails arguments.
19
+ #
20
+ # If App's name is not passed as an argument we show the command help.
21
+ #
22
+ # When the App's name is passed, we start the Panacea::Rails::Runner
23
+ def init
24
+ opts = Slop.parse do |o|
25
+ o.banner = "usage: panacea your-app-name [options]"
26
+ o.separator ""
27
+ o.string "-d", "--database", "# Options (mysql/postgresql/sqlite3/oracle/frontbase/im_db/sqlserver/jdbcmysql/jdbcsqlite3/jdbcpostgresql/jdbc)", default: "postgresql"
28
+ o.bool "--skip-namespace", "# Skip namespace (affects only isolated applications)", default: false
29
+ o.bool "--skip-yarn", "# Don't use Yarn for managing JavaScript dependencies", default: false
30
+ o.bool "--skip-git", "# Skip .gitignore file", default: false
31
+ o.bool "--skip-keeps", "# Skip source control .keep files", default: false
32
+ o.bool "--skip-action-mailer", "# Skip Action Mailer files", default: false
33
+ o.bool "--skip-active-record", "# Skip Active Record files", default: false
34
+ o.bool "--skip-active-storage", "# Skip Active Storage files", default: false
35
+ o.bool "--skip-puma", "# Skip Puma related files", default: false
36
+ o.bool "--skip-action-cable", "# Skip Action Cable files", default: false
37
+ o.bool "--skip-sprockets", "# Skip Sprockets files", default: false
38
+ o.bool "--skip-spring", "# Don't install Spring application preloader", default: false
39
+ o.bool "--skip-listen", "# Don't generate configuration that depends on the listen gem", default: false
40
+ o.bool "--skip-coffee", "# Don't use CoffeeScript", default: false
41
+ o.bool "--skip-javascript", "# Skip JavaScript files", default: false
42
+ o.bool "--skip-turbolinks", "# Skip turbolinks gem", default: false
43
+ o.bool "--skip-test", "# Skip test files", default: false
44
+ o.bool "--skip-system-test", "# Skip system test files", default: false
45
+ o.bool "--skip-bootsnap", "# Skip bootsnap gem", default: false
46
+ o.bool "--dev", "# Setup the application with Gemfile pointing to your Rails checkout", default: false
47
+ o.bool "--edge", "# Setup the application with Gemfile pointing to Rails repository", default: false
48
+ o.string "--rc", "# Path to file containing extra configuration options for rails command", default: nil
49
+ o.bool "--no-rc", "# Skip loading of extra configuration options from .railsrc file", default: false
50
+ o.bool "--api", "# Preconfigure smaller stack for API only apps", default: false
51
+ o.bool "--skip-bundle", "# Don't run bundle install", default: false
52
+ o.separator ""
53
+ o.separator "Runtime options:"
54
+ o.bool "--force", "# Overwrite files that already exist", default: false
55
+ o.bool "--pretend", "# Run but do not make any changes", default: false
56
+ o.bool "--quiet", "# Suppress status output", default: false
57
+ o.bool "--skip", "# Skip files that already exist", default: false
58
+ o.separator ""
59
+ o.separator "Panacea options:"
60
+ o.on "-v", "--version" do
61
+ puts Panacea::Rails::VERSION
62
+ exit
63
+ end
64
+ o.on "-h", "--help" do
65
+ puts o
66
+ exit
67
+ end
68
+ end
69
+
70
+ return puts(opts) if opts.arguments.empty?
71
+
72
+ Runner.call(opts.arguments.first, opts.to_hash)
73
+ end
74
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/LineLength, Metrics/BlockLength
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panacea # :nodoc:
4
+ module Rails # :nodoc:
5
+ ###
6
+ # == Panacea::Rails::ArgumentsParser
7
+ #
8
+ # This module is in charge Parsing Slop Arguments.
9
+ module ArgumentsParser
10
+ ###
11
+ # This method builds an arguments String from the Slop args Hash.
12
+ #
13
+ # The string will be passed to the `rails new` command and it will be also
14
+ # tracked if the end user agrees to share Panacea's usage information
15
+ def parse_arguments(arguments)
16
+ arguments.each_with_object([]) do |arg, parsed_args|
17
+ case arg.last.class.to_s
18
+ when "String"
19
+ parsed_args << "--#{arg.first}=#{arg.last}"
20
+ when "TrueClass"
21
+ parsed_args << "--#{arg.first}"
22
+ end
23
+ end.join(" ")
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,164 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tty/prompt"
4
+ require "yaml"
5
+ require_relative "stats"
6
+
7
+ module Panacea # :nodoc:
8
+ module Rails # :nodoc:
9
+ ###
10
+ # == Panacea::Rails::Customizer
11
+ #
12
+ # This class is in charge of asking the configuration questions.
13
+ # It saves the answers on the .panacea file.
14
+ class Customizer
15
+ # rubocop:disable Style/FormatStringToken
16
+
17
+ ###
18
+ # ASCII art displayed at the start of the Panacea command
19
+ WELCOME_MESSAGE = <<~MSG
20
+ _____________________________
21
+ | ..... // |
22
+ | _d^^^^^^^^^b_ // |
23
+ | .d'' `` |
24
+ | .p' /|
25
+ | .d' .//| Welcome to Panacea!
26
+ | .d' ----------- - `b. | You are about to boost your fresh
27
+ | :: --------------------- :: | %{app_name} Rails App
28
+ | :: --- P A N A C E A --- :: |
29
+ | :: --------------------- :: | Full documentation here: https://panacea.website
30
+ | `p. ------------------- .q' | Most of the defaults are false or disabled,
31
+ | `p. ----------------- .q' | if you want to enable a feature please answer yes
32
+ | `b. --------------- .d' |
33
+ | `q.. -------- ..p' |
34
+ | ^q........p^ |
35
+ |____________''''_____________|
36
+
37
+ MSG
38
+ # rubocop:enable Style/FormatStringToken
39
+
40
+ ###
41
+ # This method receives the App's name and the passed arguments.
42
+ #
43
+ # It creates a new instance of Panacea::Rails::Customizer class and
44
+ # executes the start instance method.
45
+ def self.start(app_name, passed_args)
46
+ new(app_name, passed_args).start
47
+ end
48
+
49
+ ###
50
+ # A Hash with the questions loaded from config/questions.yaml file
51
+ attr_reader :questions
52
+
53
+ ###
54
+ # A TTY::Prompt instance
55
+ attr_reader :prompt
56
+
57
+ ###
58
+ # A Hash where each question's answer is stored
59
+ attr_reader :answers
60
+
61
+ ###
62
+ # App's name
63
+ attr_reader :app_name
64
+
65
+ ###
66
+ # A String with the arguments passed to the Panacea command
67
+ attr_reader :passed_args
68
+
69
+ ###
70
+ # Panacea::Rails::Customizer initialize method
71
+ #
72
+ # A TTY::Prompt instance is used as default prompt.
73
+ def initialize(app_name, passed_args, prompt: TTY::Prompt.new)
74
+ @app_name = app_name
75
+ @passed_args = passed_args
76
+ @answers = {}
77
+ @prompt = prompt
78
+ end
79
+
80
+ ###
81
+ # This method shows the Welcome message.
82
+ # Then, it ask the questions using the default prompt.
83
+ # It also tracks the answers only if the end user agrees to.
84
+ # At the end, it saves the answers to the .panacea file.
85
+ def start
86
+ welcome_message
87
+ ask_questions
88
+ track_answers
89
+ save_answers
90
+ end
91
+
92
+ private
93
+
94
+ def welcome_message
95
+ message = format(WELCOME_MESSAGE, app_name: app_name.capitalize)
96
+ prompt.say(message, color: :blue)
97
+ end
98
+
99
+ def ask_questions(questions = load_questions)
100
+ questions.each do |key, question|
101
+ answer = ask_question(key, question)
102
+ subquestions = question.dig("subquestions")
103
+
104
+ ask_questions(subquestions) if !subquestions.nil? && answer
105
+ end
106
+ end
107
+
108
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Security/Eval
109
+ def ask_question(key, question)
110
+ title = question.dig("title")
111
+ default = question.dig("default")
112
+ condition = question.dig("condition")
113
+
114
+ unless condition.nil?
115
+ return unless eval(condition)
116
+ end
117
+
118
+ answer = nil
119
+
120
+ case question.dig("type")
121
+ when "boolean"
122
+ answer = prompt.yes?(title) { |q| q.default(default) }
123
+ when "range"
124
+ answer = prompt.ask(title, default: default) { |q| q.in(question.dig("range")) }
125
+ when "text"
126
+ answer = prompt.ask(title, default: default)
127
+ when "select"
128
+ answer = prompt.select(title, question.dig("options"))
129
+ else
130
+ raise StandardError, "Question type not supported."
131
+ end
132
+
133
+ update_answer(key, answer)
134
+ end
135
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Security/Eval
136
+
137
+ def update_answer(key, answer)
138
+ answers[key] = answer
139
+ end
140
+
141
+ def load_questions
142
+ questions_file = File.join(File.expand_path("../../../config", __dir__), "questions.yml")
143
+ @questions = YAML.safe_load(File.read(questions_file))
144
+ end
145
+
146
+ def save_answers
147
+ root_dir = File.expand_path("../../../", __dir__)
148
+ configurations_file = File.join(root_dir, ".panacea")
149
+
150
+ File.open(configurations_file, "w") { |f| f.write(answers.to_yaml) }
151
+ end
152
+
153
+ def track_answers
154
+ share_usage_info = answers.delete("share_usage_info")
155
+ return unless share_usage_info
156
+
157
+ params = answers.dup
158
+ params[:ruby_version] = RUBY_VERSION
159
+ params[:arguments] = passed_args
160
+ Stats.track(params)
161
+ end
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,316 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Panacea # :nodoc:
4
+ module Rails # :nodoc:
5
+ # rubocop:disable Metrics/ClassLength
6
+
7
+ ###
8
+ # == Panacea::Rails::Generator
9
+ #
10
+ # This class is in charge of grouping all the actions to be executed via Panacea::Rails::Template
11
+ class Generator
12
+ ###
13
+ # The Rails::Generators::AppGenerator context
14
+ attr_reader :app_generator
15
+
16
+ ###
17
+ # A String with Panacea's installation directory
18
+ attr_reader :root_dir
19
+
20
+ ###
21
+ # The Panacea's Configuration Hash
22
+ attr_reader :config
23
+
24
+ ###
25
+ # This methods receive the context of the Rails::Generators::AppGenerator
26
+ # So it executes all methods against it.
27
+ #
28
+ # It also receives the Panacea's Config Hash and the Panacea's Gem Root dir
29
+ # in order to update Rails::Generators::AppGenerator source paths.
30
+ def initialize(app_generator, panacea_config, root_dir)
31
+ @app_generator = app_generator
32
+ @config = panacea_config
33
+ @app_generator.instance_variable_set :@panacea, @config
34
+ @root_dir = root_dir
35
+ end
36
+
37
+ ###
38
+ # Send any unknown method to the Rails::Generators::AppGenerator context.
39
+ # That context also know Thor actions.
40
+ def method_missing(method_name, *args, &block)
41
+ super unless app_generator.respond_to?(method_name)
42
+ app_generator.send(method_name, *args, &block)
43
+ end
44
+
45
+ ###
46
+ # Whitelist of Rails::Generator::AppGenerator and Thor methods.
47
+ #
48
+ # Add here any new method to be used from Thor / Rails Generator
49
+ def respond_to_missing?(method_name, include_private = false)
50
+ %i[
51
+ after_bundle generate rails_command template
52
+ run git source_paths empty_directory append_to_file
53
+ environment application say inject_into_class
54
+ ].include?(method_name) || super
55
+ end
56
+
57
+ ###
58
+ # All the methods passed via block are executed on the Rails::Generators::AppGenerator
59
+ # after_bundle hook.
60
+ def after_bundle_hook
61
+ after_bundle do
62
+ run "spring stop"
63
+ yield self
64
+ end
65
+ end
66
+
67
+ ###
68
+ # Update Rails::Generators::AppGenerator source paths, making Panacea's Templates under
69
+ # templates/ directory available.
70
+ def update_source_paths
71
+ source_paths.unshift(root_dir)
72
+ end
73
+
74
+ ###
75
+ # Update the Gemfile
76
+ def copy_gemfile
77
+ template "templates/Gemfile.tt", "Gemfile", force: true
78
+ end
79
+
80
+ ###
81
+ # Update the README.md
82
+ def copy_readme
83
+ template "templates/README.tt", "README.md", force: true
84
+ end
85
+
86
+ ###
87
+ # Creates the PANACEA.md file
88
+ def generate_panacea_document
89
+ template "templates/PANACEA.tt", "PANACEA.md"
90
+ end
91
+
92
+ ###
93
+ # Create .rubocop.yml in generated Rails app.
94
+ def setup_rubocop
95
+ template "templates/rubocop.tt", ".rubocop.yml"
96
+ end
97
+
98
+ ###
99
+ # Setup the test suite (rspec or minitest).
100
+ def setup_test_suite
101
+ return unless config.dig("test_suite") == "rspec"
102
+
103
+ generate "rspec:install"
104
+ run "rm -r test"
105
+ end
106
+
107
+ ###
108
+ # Setup Simplecov based on the chosen test suite.
109
+ def setup_simplecov
110
+ path = if config.dig("test_suite") == "minitest"
111
+ "test/support"
112
+ else
113
+ "spec/support"
114
+ end
115
+
116
+ template "templates/simplecov.tt", "#{path}/simplecov.rb"
117
+ append_to_file ".gitignore", "\n# Ignore Coverage files \n/coverage\n"
118
+ end
119
+
120
+ ###
121
+ # Override test helper based on the chosen test suite.
122
+ def override_test_helper
123
+ if config.dig("test_suite") == "minitest"
124
+ template "templates/minitest_test_helper.tt", "test/test_helper.rb", force: true
125
+ else
126
+ template "templates/rspec_test_helper.tt", "spec/rails_helper.rb", force: true
127
+ end
128
+ end
129
+
130
+ ###
131
+ # Setup Headless Chrome Driver based on chosen test suite.
132
+ def override_application_system_test
133
+ return unless config.dig("test_suite") == "minitest"
134
+ template "templates/application_system_test.tt", "test/application_system_test_case.rb", force: true
135
+ end
136
+
137
+ ###
138
+ # Setup OJ gem.
139
+ def setup_oj
140
+ template "templates/oj_initializer.tt", "config/initializers/oj.rb"
141
+ end
142
+
143
+ ###
144
+ # Setup Dotenv gem.
145
+ def setup_dotenv
146
+ template "templates/dotenv.tt", ".env"
147
+ append_to_file ".gitignore", "\n# Ignore .env file \n.env\n"
148
+ end
149
+
150
+ ###
151
+ # Setup chosen Background Job gem.
152
+ def setup_background_job
153
+ background_job = config.dig("background_job")
154
+
155
+ application nil do
156
+ <<~CONFS
157
+ # Default adapter queue
158
+ config.active_job.queue_adapter = :#{background_job}
159
+
160
+ CONFS
161
+ end
162
+
163
+ if background_job == "sidekiq"
164
+ route "mount Sidekiq::Web => '/sidekiq'"
165
+ route "require 'sidekiq/web'"
166
+ elsif background_job == "resque"
167
+ route "mount Resque::Server, at: '/jobs'"
168
+ route "require 'resque/server'"
169
+
170
+ template("templates/Rakefile.tt", "Rakefile", force: true)
171
+ end
172
+ end
173
+
174
+ ###
175
+ # Setup the application's timezone.
176
+ def setup_timezone
177
+ timezone = config.dig("timezone").split("-").first.chomp(" ")
178
+
179
+ application nil do
180
+ <<~CONFS
181
+ # Default timezone
182
+ config.time_zone = "#{timezone}"
183
+
184
+ CONFS
185
+ end
186
+ end
187
+
188
+ ###
189
+ # Setup the application's locale.
190
+ def setup_default_locale
191
+ locale = config.dig("locale").split("- ").last
192
+
193
+ application nil do
194
+ <<~CONFS
195
+ # Default i18n locale
196
+ config.i18n.default_locale = :#{locale}
197
+
198
+ CONFS
199
+ end
200
+
201
+ template "templates/default_locale.tt", "config/locales/#{locale}.yml" if locale != "en"
202
+ end
203
+
204
+ ###
205
+ # Setup Bullet gem
206
+ def setup_bullet
207
+ environment nil, env: "development" do
208
+ <<~CONFS
209
+ # Settings for Bullet gem
210
+ config.after_initialize do
211
+ Bullet.enable = true
212
+ Bullet.alert = true
213
+ Bullet.bullet_logger = true
214
+ Bullet.console = true
215
+ Bullet.rails_logger = true
216
+ Bullet.add_footer = true
217
+ end
218
+
219
+ CONFS
220
+ end
221
+ end
222
+
223
+ ###
224
+ # Setup letter_opener gem.
225
+ def setup_letter_opener
226
+ environment nil, env: "development" do
227
+ <<~CONFS
228
+ # Settings for Letter Opener
229
+ config.action_mailer.delivery_method = :letter_opener
230
+ config.action_mailer.perform_deliveries = true
231
+ config.action_mailer.default_url_options = { host: "localhost", port: 3000 }
232
+
233
+ CONFS
234
+ end
235
+ end
236
+
237
+ ###
238
+ # Setup Devise gem.
239
+ def setup_devise
240
+ model_name = config.dig("devise_model_name").downcase
241
+ plural_model_name = model_name.downcase.pluralize
242
+
243
+ generate "devise:install"
244
+ generate "devise", model_name
245
+ generate "devise:views", plural_model_name if config.dig("devise_override_views")
246
+
247
+ rails_command "db:migrate"
248
+ end
249
+
250
+ ###
251
+ # Setup money_rails gem.
252
+ def setup_money_rails
253
+ generate "money_rails:initializer"
254
+ end
255
+
256
+ ###
257
+ # Setup Kaminari gem.
258
+ def setup_kaminari
259
+ generate "kaminari:config"
260
+ end
261
+
262
+ ###
263
+ # Setup webpacker gem.
264
+ def setup_webpack
265
+ rails_command "webpacker:install"
266
+ rails_command "webpacker:install:#{config.dig('webpack_type')}" if config.dig("webpack_type") != "none"
267
+ end
268
+
269
+ ###
270
+ # Setup foreman gem
271
+ def setup_foreman
272
+ run "gem install foreman" unless system("gem list -i foreman")
273
+
274
+ template("templates/Procfile.tt", "Procfile")
275
+ end
276
+
277
+ ###
278
+ # Setup Pundit gem
279
+ def setup_pundit
280
+ generate "pundit:install"
281
+ inject_into_class "app/controllers/application_controller.rb", "ApplicationController", " include Pundit\n"
282
+ end
283
+
284
+ ###
285
+ # Creates chosen Git hook.
286
+ def setup_githook
287
+ hook_file = ".git/hooks/#{config.dig('githook_type')}"
288
+ template "templates/githook.tt", hook_file
289
+ run "chmod ug+x #{hook_file}"
290
+ end
291
+
292
+ ###
293
+ # This needs to be run before commiting.
294
+ #
295
+ # Fix existing application's style offenses.
296
+ def fix_offenses!
297
+ run "rubocop -a --format=simple"
298
+ end
299
+
300
+ ###
301
+ # Commit only if end users want to.
302
+ def commit!
303
+ git add: "."
304
+ git commit: "-m '#{config.dig('commit_msg')}'"
305
+ end
306
+
307
+ ###
308
+ # Display good bye message.
309
+ def bye_message
310
+ message = "Panacea's work is done, enjoy!"
311
+ say "\n\n\e[34m#{message}\e[0m"
312
+ end
313
+ end
314
+ # rubocop:enable Metrics/ClassLength
315
+ end
316
+ end