railties 7.0.8 → 7.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (163) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +585 -209
  3. data/MIT-LICENSE +1 -1
  4. data/RDOC_MAIN.md +99 -0
  5. data/README.rdoc +4 -4
  6. data/lib/minitest/rails_plugin.rb +63 -0
  7. data/lib/rails/api/task.rb +35 -4
  8. data/lib/rails/app_updater.rb +1 -1
  9. data/lib/rails/application/bootstrap.rb +12 -3
  10. data/lib/rails/application/configuration.rb +179 -67
  11. data/lib/rails/application/default_middleware_stack.rb +8 -2
  12. data/lib/rails/application/dummy_config.rb +19 -0
  13. data/lib/rails/application/finisher.rb +40 -33
  14. data/lib/rails/application.rb +112 -24
  15. data/lib/rails/backtrace_cleaner.rb +1 -1
  16. data/lib/rails/cli.rb +5 -2
  17. data/lib/rails/command/actions.rb +10 -12
  18. data/lib/rails/command/base.rb +55 -53
  19. data/lib/rails/command/environment_argument.rb +32 -16
  20. data/lib/rails/command/helpers/editor.rb +17 -12
  21. data/lib/rails/command.rb +84 -33
  22. data/lib/rails/commands/about/about_command.rb +14 -0
  23. data/lib/rails/commands/application/application_command.rb +2 -0
  24. data/lib/rails/commands/console/console_command.rb +14 -14
  25. data/lib/rails/commands/credentials/USAGE +53 -55
  26. data/lib/rails/commands/credentials/credentials_command/diffing.rb +5 -3
  27. data/lib/rails/commands/credentials/credentials_command.rb +64 -70
  28. data/lib/rails/commands/db/system/change/change_command.rb +2 -1
  29. data/lib/rails/commands/dbconsole/dbconsole_command.rb +25 -115
  30. data/lib/rails/commands/destroy/destroy_command.rb +3 -2
  31. data/lib/rails/commands/dev/dev_command.rb +1 -6
  32. data/lib/rails/commands/encrypted/USAGE +15 -20
  33. data/lib/rails/commands/encrypted/encrypted_command.rb +46 -35
  34. data/lib/rails/commands/gem_help/USAGE +16 -0
  35. data/lib/rails/commands/gem_help/gem_help_command.rb +13 -0
  36. data/lib/rails/commands/generate/generate_command.rb +2 -2
  37. data/lib/rails/commands/help/USAGE +13 -13
  38. data/lib/rails/commands/help/help_command.rb +21 -2
  39. data/lib/rails/commands/initializers/initializers_command.rb +1 -4
  40. data/lib/rails/commands/middleware/middleware_command.rb +17 -0
  41. data/lib/rails/commands/new/new_command.rb +2 -0
  42. data/lib/rails/commands/notes/notes_command.rb +2 -1
  43. data/lib/rails/commands/plugin/plugin_command.rb +2 -0
  44. data/lib/rails/commands/rake/rake_command.rb +25 -22
  45. data/lib/rails/commands/restart/restart_command.rb +14 -0
  46. data/lib/rails/commands/routes/routes_command.rb +13 -1
  47. data/lib/rails/commands/runner/USAGE +14 -12
  48. data/lib/rails/commands/runner/runner_command.rb +32 -20
  49. data/lib/rails/commands/secret/secret_command.rb +13 -0
  50. data/lib/rails/commands/secrets/USAGE +44 -49
  51. data/lib/rails/commands/secrets/secrets_command.rb +19 -38
  52. data/lib/rails/commands/server/server_command.rb +32 -31
  53. data/lib/rails/commands/test/USAGE +14 -0
  54. data/lib/rails/commands/test/test_command.rb +56 -14
  55. data/lib/rails/commands/unused_routes/unused_routes_command.rb +75 -0
  56. data/lib/rails/commands/version/version_command.rb +1 -0
  57. data/lib/rails/configuration.rb +5 -5
  58. data/lib/rails/console/app.rb +1 -4
  59. data/lib/rails/deprecator.rb +7 -0
  60. data/lib/rails/engine/configuration.rb +5 -0
  61. data/lib/rails/engine.rb +32 -11
  62. data/lib/rails/gem_version.rb +4 -4
  63. data/lib/rails/generators/actions.rb +6 -15
  64. data/lib/rails/generators/active_model.rb +2 -2
  65. data/lib/rails/generators/app_base.rb +354 -83
  66. data/lib/rails/generators/app_name.rb +3 -14
  67. data/lib/rails/generators/base.rb +12 -4
  68. data/lib/rails/generators/database.rb +19 -1
  69. data/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt +1 -1
  70. data/lib/rails/generators/generated_attribute.rb +2 -0
  71. data/lib/rails/generators/migration.rb +1 -2
  72. data/lib/rails/generators/model_helpers.rb +2 -1
  73. data/lib/rails/generators/rails/app/USAGE +15 -6
  74. data/lib/rails/generators/rails/app/app_generator.rb +84 -60
  75. data/lib/rails/generators/rails/app/templates/Dockerfile.tt +107 -0
  76. data/lib/rails/generators/rails/app/templates/Gemfile.tt +8 -10
  77. data/lib/rails/generators/rails/app/templates/app/views/layouts/mailer.html.erb.tt +1 -1
  78. data/lib/rails/generators/rails/app/templates/bin/setup.tt +10 -1
  79. data/lib/rails/generators/rails/app/templates/config/application.rb.tt +4 -17
  80. data/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt +3 -3
  81. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +0 -2
  82. data/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +3 -3
  83. data/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +59 -0
  84. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +10 -2
  85. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +28 -24
  86. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +11 -7
  87. data/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +2 -0
  88. data/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt +2 -2
  89. data/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt +1 -1
  90. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_1.rb.tt +223 -0
  91. data/lib/rails/generators/rails/app/templates/config/initializers/permissions_policy.rb.tt +11 -9
  92. data/lib/rails/generators/rails/app/templates/config/locales/en.yml +11 -13
  93. data/lib/rails/generators/rails/app/templates/config/puma.rb.tt +10 -19
  94. data/lib/rails/generators/rails/app/templates/config/routes.rb.tt +4 -0
  95. data/lib/rails/generators/rails/app/templates/db/seeds.rb.tt +6 -4
  96. data/lib/rails/generators/rails/app/templates/docker-entrypoint.tt +10 -0
  97. data/lib/rails/generators/rails/app/templates/dockerignore.tt +43 -0
  98. data/lib/rails/generators/rails/app/templates/gitignore.tt +1 -9
  99. data/lib/rails/generators/rails/app/templates/node-version.tt +1 -0
  100. data/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt +10 -8
  101. data/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +9 -7
  102. data/lib/rails/generators/rails/application_record/application_record_generator.rb +4 -0
  103. data/lib/rails/generators/rails/benchmark/benchmark_generator.rb +2 -1
  104. data/lib/rails/generators/rails/controller/USAGE +12 -4
  105. data/lib/rails/generators/rails/controller/controller_generator.rb +5 -0
  106. data/lib/rails/generators/rails/controller/templates/controller.rb.tt +1 -1
  107. data/lib/rails/generators/rails/credentials/credentials_generator.rb +29 -24
  108. data/lib/rails/generators/rails/credentials/templates/credentials.yml.tt +8 -0
  109. data/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb +1 -2
  110. data/lib/rails/generators/rails/migration/USAGE +21 -11
  111. data/lib/rails/generators/rails/model/model_generator.rb +4 -0
  112. data/lib/rails/generators/rails/plugin/USAGE +17 -6
  113. data/lib/rails/generators/rails/plugin/plugin_generator.rb +5 -15
  114. data/lib/rails/generators/rails/plugin/templates/Gemfile.tt +2 -2
  115. data/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt +1 -1
  116. data/lib/rails/generators/rails/plugin/templates/bin/rails.tt +1 -17
  117. data/lib/rails/generators/rails/plugin/templates/gitignore.tt +0 -2
  118. data/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt +4 -4
  119. data/lib/rails/generators/rails/resource/resource_generator.rb +6 -0
  120. data/lib/rails/generators/rails/scaffold/scaffold_generator.rb +2 -1
  121. data/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +1 -1
  122. data/lib/rails/generators/test_case.rb +2 -2
  123. data/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +1 -1
  124. data/lib/rails/generators/testing/{behaviour.rb → behavior.rb} +4 -1
  125. data/lib/rails/generators.rb +5 -13
  126. data/lib/rails/health_controller.rb +55 -0
  127. data/lib/rails/info.rb +1 -1
  128. data/lib/rails/info_controller.rb +31 -11
  129. data/lib/rails/mailers_controller.rb +15 -5
  130. data/lib/rails/paths.rb +13 -10
  131. data/lib/rails/rack/logger.rb +15 -12
  132. data/lib/rails/rackup/server.rb +15 -0
  133. data/lib/rails/railtie/configuration.rb +14 -1
  134. data/lib/rails/railtie.rb +18 -18
  135. data/lib/rails/ruby_version_check.rb +2 -0
  136. data/lib/rails/source_annotation_extractor.rb +67 -18
  137. data/lib/rails/tasks/engine.rake +8 -8
  138. data/lib/rails/tasks/framework.rake +4 -10
  139. data/lib/rails/tasks/log.rake +1 -1
  140. data/lib/rails/tasks/misc.rake +3 -14
  141. data/lib/rails/tasks/statistics.rake +5 -4
  142. data/lib/rails/tasks/tmp.rake +5 -5
  143. data/lib/rails/tasks/zeitwerk.rake +1 -1
  144. data/lib/rails/tasks.rb +0 -2
  145. data/lib/rails/templates/rails/mailers/email.html.erb +25 -0
  146. data/lib/rails/templates/rails/mailers/index.html.erb +14 -7
  147. data/lib/rails/templates/rails/mailers/mailer.html.erb +11 -5
  148. data/lib/rails/templates/rails/welcome/index.html.erb +1 -0
  149. data/lib/rails/test_help.rb +7 -7
  150. data/lib/rails/test_unit/line_filtering.rb +1 -1
  151. data/lib/rails/test_unit/reporter.rb +6 -2
  152. data/lib/rails/test_unit/runner.rb +36 -18
  153. data/lib/rails/test_unit/test_parser.rb +88 -0
  154. data/lib/rails/test_unit/testing.rake +13 -33
  155. data/lib/rails/version.rb +1 -1
  156. data/lib/rails.rb +15 -15
  157. metadata +65 -30
  158. data/RDOC_MAIN.rdoc +0 -97
  159. data/lib/rails/application/dummy_erb_compiler.rb +0 -18
  160. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_0.rb.tt +0 -143
  161. data/lib/rails/generators/rails/model/USAGE +0 -113
  162. data/lib/rails/tasks/middleware.rake +0 -9
  163. data/lib/rails/tasks/restart.rake +0 -9
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "digest/md5"
5
5
  require "rails/version" unless defined?(Rails::VERSION)
6
6
  require "open-uri"
7
+ require "tsort"
7
8
  require "uri"
8
9
  require "rails/generators"
9
10
  require "active_support/core_ext/array/extract_options"
@@ -14,6 +15,9 @@ module Rails
14
15
  include Database
15
16
  include AppName
16
17
 
18
+ NODE_LTS_VERSION = "18.15.0"
19
+ BUN_VERSION = "1.0.1"
20
+
17
21
  attr_accessor :rails_template
18
22
  add_shebang_option!
19
23
 
@@ -24,82 +28,95 @@ module Rails
24
28
  end
25
29
 
26
30
  def self.add_shared_options_for(name)
31
+ class_option :name, type: :string, aliases: "-n",
32
+ desc: "Name of the app"
33
+
27
34
  class_option :template, type: :string, aliases: "-m",
28
35
  desc: "Path to some #{name} template (can be a filesystem path or URL)"
29
36
 
30
37
  class_option :database, type: :string, aliases: "-d", default: "sqlite3",
31
38
  desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
32
39
 
33
- class_option :skip_git, type: :boolean, aliases: "-G", default: false,
34
- desc: "Skip .gitignore file"
40
+ class_option :skip_git, type: :boolean, aliases: "-G", default: nil,
41
+ desc: "Skip git init, .gitignore and .gitattributes"
35
42
 
36
- class_option :skip_keeps, type: :boolean, default: false,
43
+ class_option :skip_docker, type: :boolean, default: nil,
44
+ desc: "Skip Dockerfile, .dockerignore and bin/docker-entrypoint"
45
+
46
+ class_option :skip_keeps, type: :boolean, default: nil,
37
47
  desc: "Skip source control .keep files"
38
48
 
39
49
  class_option :skip_action_mailer, type: :boolean, aliases: "-M",
40
- default: false,
50
+ default: nil,
41
51
  desc: "Skip Action Mailer files"
42
52
 
43
- class_option :skip_action_mailbox, type: :boolean, default: false,
53
+ class_option :skip_action_mailbox, type: :boolean, default: nil,
44
54
  desc: "Skip Action Mailbox gem"
45
55
 
46
- class_option :skip_action_text, type: :boolean, default: false,
56
+ class_option :skip_action_text, type: :boolean, default: nil,
47
57
  desc: "Skip Action Text gem"
48
58
 
49
- class_option :skip_active_record, type: :boolean, aliases: "-O", default: false,
59
+ class_option :skip_active_record, type: :boolean, aliases: "-O", default: nil,
50
60
  desc: "Skip Active Record files"
51
61
 
52
- class_option :skip_active_job, type: :boolean, default: false,
62
+ class_option :skip_active_job, type: :boolean, default: nil,
53
63
  desc: "Skip Active Job"
54
64
 
55
- class_option :skip_active_storage, type: :boolean, default: false,
65
+ class_option :skip_active_storage, type: :boolean, default: nil,
56
66
  desc: "Skip Active Storage files"
57
67
 
58
- class_option :skip_action_cable, type: :boolean, aliases: "-C", default: false,
68
+ class_option :skip_action_cable, type: :boolean, aliases: "-C", default: nil,
59
69
  desc: "Skip Action Cable files"
60
70
 
61
- class_option :skip_asset_pipeline, type: :boolean, aliases: "-A", default: false
71
+ class_option :skip_asset_pipeline, type: :boolean, aliases: "-A", default: nil
62
72
 
63
73
  class_option :asset_pipeline, type: :string, aliases: "-a", default: "sprockets",
64
74
  desc: "Choose your asset pipeline [options: sprockets (default), propshaft]"
65
75
 
66
- class_option :skip_javascript, type: :boolean, aliases: "-J", default: name == "plugin",
76
+ class_option :skip_javascript, type: :boolean, aliases: ["-J", "--skip-js"], default: (true if name == "plugin"),
67
77
  desc: "Skip JavaScript files"
68
78
 
69
- class_option :skip_hotwire, type: :boolean, default: false,
79
+ class_option :skip_hotwire, type: :boolean, default: nil,
70
80
  desc: "Skip Hotwire integration"
71
81
 
72
- class_option :skip_jbuilder, type: :boolean, default: false,
82
+ class_option :skip_jbuilder, type: :boolean, default: nil,
73
83
  desc: "Skip jbuilder gem"
74
84
 
75
- class_option :skip_test, type: :boolean, aliases: "-T", default: false,
85
+ class_option :skip_test, type: :boolean, aliases: "-T", default: nil,
76
86
  desc: "Skip test files"
77
87
 
78
- class_option :skip_system_test, type: :boolean, default: false,
88
+ class_option :skip_system_test, type: :boolean, default: nil,
79
89
  desc: "Skip system test files"
80
90
 
81
- class_option :skip_bootsnap, type: :boolean, default: false,
91
+ class_option :skip_bootsnap, type: :boolean, default: nil,
82
92
  desc: "Skip bootsnap gem"
83
93
 
84
- class_option :dev, type: :boolean, default: false,
94
+ class_option :skip_dev_gems, type: :boolean, default: nil,
95
+ desc: "Skip development gems (e.g., web-console)"
96
+
97
+ class_option :dev, type: :boolean, default: nil,
85
98
  desc: "Set up the #{name} with Gemfile pointing to your Rails checkout"
86
99
 
87
- class_option :edge, type: :boolean, default: false,
88
- desc: "Set up the #{name} with Gemfile pointing to Rails repository"
100
+ class_option :edge, type: :boolean, default: nil,
101
+ desc: "Set up the #{name} with a Gemfile pointing to the #{edge_branch} branch on the Rails repository"
89
102
 
90
- class_option :main, type: :boolean, default: false, aliases: "--master",
103
+ class_option :main, type: :boolean, default: nil, aliases: "--master",
91
104
  desc: "Set up the #{name} with Gemfile pointing to Rails repository main branch"
92
105
 
93
106
  class_option :rc, type: :string, default: nil,
94
107
  desc: "Path to file containing extra configuration options for rails command"
95
108
 
96
- class_option :no_rc, type: :boolean, default: false,
109
+ class_option :no_rc, type: :boolean, default: nil,
97
110
  desc: "Skip loading of extra configuration options from .railsrc file"
98
111
 
99
112
  class_option :help, type: :boolean, aliases: "-h", group: :rails,
100
113
  desc: "Show this help message and quit"
101
114
  end
102
115
 
116
+ def self.edge_branch # :nodoc:
117
+ Rails.gem_version.prerelease? ? "main" : [*Rails.gem_version.segments.first(2), "stable"].join("-")
118
+ end
119
+
103
120
  def initialize(positional_argv, option_argv, *)
104
121
  @argv = [*positional_argv, *option_argv]
105
122
  @gem_filter = lambda { |gem| true }
@@ -117,7 +134,6 @@ module Rails
117
134
  hotwire_gemfile_entry,
118
135
  css_gemfile_entry,
119
136
  jbuilder_gemfile_entry,
120
- psych_gemfile_entry,
121
137
  cable_gemfile_entry,
122
138
  ].flatten.compact.select(&@gem_filter)
123
139
  end
@@ -134,6 +150,89 @@ module Rails
134
150
  builder.public_send(meth, *args) if builder.respond_to?(meth)
135
151
  end
136
152
 
153
+ def deduce_implied_options(options, option_reasons, meta_options)
154
+ active = options.transform_values { |value| [] if value }.compact
155
+ irrevocable = (active.keys - meta_options).to_set
156
+
157
+ deduction_order = TSort.tsort(
158
+ ->(&block) { option_reasons.each_key(&block) },
159
+ ->(key, &block) { option_reasons[key]&.each(&block) }
160
+ )
161
+
162
+ deduction_order.each do |name|
163
+ active_reasons = option_reasons[name].to_a.select(&active)
164
+ active[name] ||= active_reasons if active_reasons.any?
165
+ irrevocable << name if active_reasons.any?(irrevocable)
166
+ end
167
+
168
+ revoked = options.select { |name, value| value == false }.keys.to_set - irrevocable
169
+ deduction_order.reverse_each do |name|
170
+ revoked += option_reasons[name].to_a if revoked.include?(name)
171
+ end
172
+ revoked -= meta_options
173
+
174
+ active.filter_map do |name, reasons|
175
+ unless revoked.include?(name) || reasons.all?(revoked)
176
+ [name, reasons - revoked.to_a]
177
+ end
178
+ end.to_h
179
+ end
180
+
181
+ OPTION_IMPLICATIONS = { # :nodoc:
182
+ skip_active_job: [:skip_action_mailer, :skip_active_storage],
183
+ skip_active_record: [:skip_active_storage],
184
+ skip_active_storage: [:skip_action_mailbox, :skip_action_text],
185
+ skip_javascript: [:skip_hotwire],
186
+ }
187
+
188
+ # ==== Options
189
+ #
190
+ # [+:meta_options+]
191
+ # A list of generator options which only serve to trigger other options.
192
+ # These options should have no other effects, and will be treated
193
+ # transparently when revoking other options.
194
+ #
195
+ # For example: --minimal implies both --skip-active-job and
196
+ # --skip-active-storage. Also, --skip-active-job by itself implies
197
+ # --skip-active-storage. If --skip-active-job is explicitly
198
+ # specified, --no-skip-active-storage should raise an error. But, if
199
+ # only --minimal is specified, --no-skip-active-storage should "undo"
200
+ # the implied --skip-active-job. This can be accomplished by passing
201
+ # <tt>meta_options: [:minimal]</tt>.
202
+ #
203
+ # In contrast, --api is not a meta option because it does other things
204
+ # besides implying options such as --skip-asset-pipeline. (And so --api
205
+ # with --no-skip-asset-pipeline should raise an error.)
206
+ def imply_options(option_implications = OPTION_IMPLICATIONS, meta_options: [])
207
+ option_reasons = {}
208
+ option_implications.each do |reason, implications|
209
+ implications.each do |implication|
210
+ (option_reasons[implication.to_s] ||= []) << reason.to_s
211
+ end
212
+ end
213
+
214
+ @implied_options = deduce_implied_options(options, option_reasons, meta_options.map(&:to_s))
215
+ @implied_options_conflicts = @implied_options.keys.select { |name| options[name] == false }
216
+ self.options = options.merge(@implied_options.transform_values { true }).freeze
217
+ end
218
+
219
+ def report_implied_options
220
+ return if @implied_options.blank?
221
+
222
+ say "Based on the specified options, the following options will also be activated:"
223
+ say ""
224
+ @implied_options.each do |name, reasons|
225
+ due_to = reasons.map { |reason| "--#{reason.dasherize}" }.join(", ")
226
+ say " --#{name.dasherize} [due to #{due_to}]"
227
+ if @implied_options_conflicts.include?(name)
228
+ say " ERROR: Conflicts with --no-#{name.dasherize}", :red
229
+ end
230
+ end
231
+ say ""
232
+
233
+ raise "Cannot proceed due to conflicting options" if @implied_options_conflicts.any?
234
+ end
235
+
137
236
  def create_root # :doc:
138
237
  valid_const?
139
238
 
@@ -149,15 +248,13 @@ module Rails
149
248
 
150
249
  def set_default_accessors! # :doc:
151
250
  self.destination_root = File.expand_path(app_path, destination_root)
152
- self.rails_template = \
153
- case options[:template]
154
- when /^https?:\/\//
155
- options[:template]
156
- when String
157
- File.expand_path(`echo #{options[:template]}`.strip)
158
- else
159
- options[:template]
160
- end
251
+
252
+ if options[:template].is_a?(String) && !options[:template].match?(/^https?:\/\//)
253
+ interpolated = options[:template].gsub(/\$(\w+)|\$\{\g<1>\}|%\g<1>%/) { |m| ENV[$1] || m }
254
+ self.rails_template = File.expand_path(interpolated)
255
+ else
256
+ self.rails_template = options[:template]
257
+ end
161
258
  end
162
259
 
163
260
  def database_gemfile_entry # :doc:
@@ -169,11 +266,11 @@ module Rails
169
266
  end
170
267
 
171
268
  def web_server_gemfile_entry # :doc:
172
- GemfileEntry.new "puma", "~> 5.0", "Use the Puma web server [https://github.com/puma/puma]"
269
+ GemfileEntry.new "puma", ">= 5.0", "Use the Puma web server [https://github.com/puma/puma]"
173
270
  end
174
271
 
175
272
  def asset_pipeline_gemfile_entry
176
- return if options[:skip_asset_pipeline]
273
+ return if skip_asset_pipeline?
177
274
 
178
275
  if options[:asset_pipeline] == "sprockets"
179
276
  GemfileEntry.floats "sprockets-rails",
@@ -183,19 +280,40 @@ module Rails
183
280
  end
184
281
  end
185
282
 
283
+ def required_railties
284
+ @required_railties ||= {
285
+ "active_model/railtie" => true,
286
+ "active_job/railtie" => !options[:skip_active_job],
287
+ "active_record/railtie" => !options[:skip_active_record],
288
+ "active_storage/engine" => !options[:skip_active_storage],
289
+ "action_controller/railtie" => true,
290
+ "action_mailer/railtie" => !options[:skip_action_mailer],
291
+ "action_mailbox/engine" => !options[:skip_action_mailbox],
292
+ "action_text/engine" => !options[:skip_action_text],
293
+ "action_view/railtie" => true,
294
+ "action_cable/engine" => !options[:skip_action_cable],
295
+ "rails/test_unit/railtie" => !options[:skip_test],
296
+ }
297
+ end
298
+
186
299
  def include_all_railties? # :doc:
187
- [
188
- options.values_at(
189
- :skip_active_record,
190
- :skip_test,
191
- :skip_action_cable,
192
- :skip_active_job
193
- ),
194
- skip_active_storage?,
195
- skip_action_mailer?,
196
- skip_action_mailbox?,
197
- skip_action_text?
198
- ].flatten.none?
300
+ required_railties.values.all?
301
+ end
302
+
303
+ def rails_require_statement
304
+ if include_all_railties?
305
+ %(require "rails/all")
306
+ else
307
+ require_statements = required_railties.map do |railtie, required|
308
+ %(#{"# " if !required}require "#{railtie}")
309
+ end
310
+
311
+ <<~RUBY.strip
312
+ require "rails"
313
+ # Pick the frameworks you want:
314
+ #{require_statements.join("\n")}
315
+ RUBY
316
+ end
199
317
  end
200
318
 
201
319
  def comment_if(value) # :doc:
@@ -216,35 +334,39 @@ module Rails
216
334
  end
217
335
 
218
336
  def sqlite3? # :doc:
219
- !options[:skip_active_record] && options[:database] == "sqlite3"
337
+ !skip_active_record? && options[:database] == "sqlite3"
338
+ end
339
+
340
+ def skip_active_record? # :doc:
341
+ options[:skip_active_record]
220
342
  end
221
343
 
222
344
  def skip_active_storage? # :doc:
223
- options[:skip_active_storage] || options[:skip_active_record] || options[:skip_active_job]
345
+ options[:skip_active_storage]
224
346
  end
225
347
 
226
348
  def skip_action_mailer? # :doc:
227
- options[:skip_action_mailer] || options[:skip_active_job]
349
+ options[:skip_action_mailer]
228
350
  end
229
351
 
230
352
  def skip_action_mailbox? # :doc:
231
- options[:skip_action_mailbox] || skip_active_storage?
353
+ options[:skip_action_mailbox]
232
354
  end
233
355
 
234
356
  def skip_action_text? # :doc:
235
- options[:skip_action_text] || skip_active_storage?
357
+ options[:skip_action_text]
236
358
  end
237
359
 
238
- def skip_dev_gems? # :doc:
239
- options[:skip_dev_gems]
360
+ def skip_asset_pipeline? # :doc:
361
+ options[:skip_asset_pipeline]
240
362
  end
241
363
 
242
364
  def skip_sprockets?
243
- options[:skip_asset_pipeline] || options[:asset_pipeline] != "sprockets"
365
+ skip_asset_pipeline? || options[:asset_pipeline] != "sprockets"
244
366
  end
245
367
 
246
368
  def skip_propshaft?
247
- options[:skip_asset_pipeline] || options[:asset_pipeline] != "propshaft"
369
+ skip_asset_pipeline? || options[:asset_pipeline] != "propshaft"
248
370
  end
249
371
 
250
372
 
@@ -284,6 +406,10 @@ module Rails
284
406
  end
285
407
  end
286
408
 
409
+ def gem_ruby_version
410
+ Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.13") ? Gem.ruby_version : RUBY_VERSION
411
+ end
412
+
287
413
  def rails_prerelease?
288
414
  options.dev? || options.edge? || options.main?
289
415
  end
@@ -292,7 +418,6 @@ module Rails
292
418
  if options.dev?
293
419
  GemfileEntry.path("rails", Rails::Generators::RAILS_DEV_PATH, "Use local checkout of Rails")
294
420
  elsif options.edge?
295
- edge_branch = Rails.gem_version.prerelease? ? "main" : [*Rails.gem_version.segments.first(2), "stable"].join("-")
296
421
  GemfileEntry.github("rails", "rails/rails", edge_branch, "Use specific branch of Rails")
297
422
  elsif options.main?
298
423
  GemfileEntry.github("rails", "rails/rails", "main", "Use main development branch of Rails")
@@ -323,7 +448,7 @@ module Rails
323
448
  def javascript_gemfile_entry
324
449
  return if options[:skip_javascript]
325
450
 
326
- if adjusted_javascript_option == "importmap"
451
+ if options[:javascript] == "importmap"
327
452
  GemfileEntry.floats "importmap-rails", "Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]"
328
453
  else
329
454
  GemfileEntry.floats "jsbundling-rails", "Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]"
@@ -331,7 +456,7 @@ module Rails
331
456
  end
332
457
 
333
458
  def hotwire_gemfile_entry
334
- return if options[:skip_javascript] || options[:skip_hotwire]
459
+ return if options[:skip_hotwire]
335
460
 
336
461
  turbo_rails_entry =
337
462
  GemfileEntry.floats "turbo-rails", "Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]"
@@ -342,43 +467,141 @@ module Rails
342
467
  [ turbo_rails_entry, stimulus_rails_entry ]
343
468
  end
344
469
 
470
+ def using_js_runtime?
471
+ (options[:javascript] && !%w[importmap].include?(options[:javascript])) ||
472
+ (options[:css] && !%w[tailwind sass].include?(options[:css]))
473
+ end
474
+
345
475
  def using_node?
346
- options[:javascript] && options[:javascript] != "importmap"
476
+ using_js_runtime? && !%w[bun].include?(options[:javascript])
347
477
  end
348
478
 
349
- # CSS processors other than Tailwind require a node-based JavaScript environment. So overwrite the normal JS default
350
- # if one such processor has been specified.
351
- def adjusted_javascript_option
352
- if options[:css] && options[:css] != "tailwind" && options[:javascript] == "importmap"
353
- "esbuild"
354
- else
355
- options[:javascript]
479
+ def using_bun?
480
+ using_js_runtime? && %w[bun].include?(options[:javascript])
481
+ end
482
+
483
+ def node_version
484
+ if using_node?
485
+ ENV.fetch("NODE_VERSION") do
486
+ `node --version`[/\d+\.\d+\.\d+/]
487
+ rescue
488
+ NODE_LTS_VERSION
489
+ end
490
+ end
491
+ end
492
+
493
+ def dockerfile_yarn_version
494
+ using_node? and `yarn --version`[/\d+\.\d+\.\d+/]
495
+ rescue
496
+ "latest"
497
+ end
498
+
499
+ def dockerfile_bun_version
500
+ using_bun? and "bun-v#{`bun --version`[/\d+\.\d+\.\d+/]}"
501
+ rescue
502
+ BUN_VERSION
503
+ end
504
+
505
+ def dockerfile_binfile_fixups
506
+ # binfiles may have OS specific paths to ruby. Normalize them.
507
+ shebangs = Dir["bin/*"].map { |file| IO.read(file).lines.first }.join
508
+ rubies = shebangs.scan(%r{#!/usr/bin/env (ruby.*)}).flatten.uniq
509
+
510
+ binfixups = (rubies - %w(ruby)).map do |ruby|
511
+ "sed -i 's/#{Regexp.quote(ruby)}$/ruby/' bin/*"
512
+ end
513
+
514
+ # Windows line endings will cause scripts to fail. If any
515
+ # or found OR this generation is run on a windows platform
516
+ # and there are other binfixups required, then convert
517
+ # line endings. This avoids adding unnecessary fixups if
518
+ # none are required, but prepares for the need to do the
519
+ # fix line endings if other fixups are required.
520
+ has_cr = Dir["bin/*"].any? { |file| IO.read(file).include? "\r" }
521
+ if has_cr || (Gem.win_platform? && !binfixups.empty?)
522
+ binfixups.unshift 'sed -i "s/\r$//g" bin/*'
523
+ end
524
+
525
+ # Windows file systems may not have the concept of executable.
526
+ # In such cases, fix up during the build.
527
+ unless Dir["bin/*"].all? { |file| File.executable? file }
528
+ binfixups.unshift "chmod +x bin/*"
356
529
  end
530
+
531
+ binfixups
532
+ end
533
+
534
+ def dockerfile_build_packages
535
+ # start with the essentials
536
+ packages = %w(build-essential git pkg-config)
537
+
538
+ # add database support
539
+ packages << build_package_for_database unless skip_active_record?
540
+
541
+ # ActiveStorage preview support
542
+ packages << "libvips" unless skip_active_storage?
543
+
544
+ packages << "curl" if using_js_runtime?
545
+
546
+ packages << "unzip" if using_bun?
547
+
548
+ # node support, including support for building native modules
549
+ if using_node?
550
+ packages << "node-gyp" # pkg-config already listed above
551
+
552
+ # module build process depends on Python, and debian changed
553
+ # how python is installed with the bullseye release. Below
554
+ # is based on debian release included with the Ruby images on
555
+ # Dockerhub.
556
+ case Gem.ruby_version.to_s
557
+ when /^2\.7/
558
+ bullseye = Gem.ruby_version >= Gem::Version.new("2.7.4")
559
+ when /^3\.0/
560
+ bullseye = Gem.ruby_version >= Gem::Version.new("3.0.2")
561
+ else
562
+ bullseye = true
563
+ end
564
+
565
+ if bullseye
566
+ packages << "python-is-python3"
567
+ else
568
+ packages << "python"
569
+ end
570
+ end
571
+
572
+ packages.compact.sort
573
+ end
574
+
575
+ def dockerfile_deploy_packages
576
+ # Add curl to work with the default healthcheck strategy in Kamal
577
+ packages = ["curl"]
578
+
579
+ # ActiveRecord databases
580
+ packages << deploy_package_for_database unless skip_active_record?
581
+
582
+ # ActiveStorage preview support
583
+ packages << "libvips" unless skip_active_storage?
584
+
585
+ packages.compact.sort
357
586
  end
358
587
 
359
588
  def css_gemfile_entry
360
589
  return unless options[:css]
361
590
 
362
- if !using_node? && options[:css] == "tailwind"
591
+ if !using_js_runtime? && options[:css] == "tailwind"
363
592
  GemfileEntry.floats "tailwindcss-rails", "Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]"
593
+ elsif !using_js_runtime? && options[:css] == "sass"
594
+ GemfileEntry.floats "dartsass-rails", "Use Dart SASS [https://github.com/rails/dartsass-rails]"
364
595
  else
365
596
  GemfileEntry.floats "cssbundling-rails", "Bundle and process CSS [https://github.com/rails/cssbundling-rails]"
366
597
  end
367
598
  end
368
599
 
369
- def psych_gemfile_entry
370
- return unless defined?(Rubinius)
371
-
372
- comment = "Use Psych as the YAML engine, instead of Syck, so serialized " \
373
- "data can be read safely from different rubies (see http://git.io/uuLVag)"
374
- GemfileEntry.new("psych", "~> 2.0", comment, platforms: :rbx)
375
- end
376
-
377
600
  def cable_gemfile_entry
378
601
  return if options[:skip_action_cable]
379
602
 
380
603
  comment = "Use Redis adapter to run Action Cable in production"
381
- GemfileEntry.new("redis", "~> 4.0", comment, {}, true)
604
+ GemfileEntry.new("redis", ">= 4.0.1", comment, {}, true)
382
605
  end
383
606
 
384
607
  def bundle_command(command, env = {})
@@ -411,6 +634,10 @@ module Rails
411
634
  !(options[:skip_bundle] || options[:pretend])
412
635
  end
413
636
 
637
+ def bundler_windows_platforms
638
+ Gem.rubygems_version >= Gem::Version.new("3.3.22") ? "windows" : "mswin mswin64 mingw x64_mingw"
639
+ end
640
+
414
641
  def depends_on_system_test?
415
642
  !(options[:skip_system_test] || options[:skip_test] || options[:api])
416
643
  end
@@ -431,7 +658,8 @@ module Rails
431
658
 
432
659
  run_bundle
433
660
 
434
- @argv[0] = destination_root
661
+ @argv.delete_at(@argv.index(app_path))
662
+ @argv.unshift(destination_root)
435
663
  require "shellwords"
436
664
  bundle_command("exec rails #{self_command} #{Shellwords.join(@argv)}")
437
665
  exit
@@ -442,20 +670,32 @@ module Rails
442
670
  end
443
671
 
444
672
  def run_bundle
445
- bundle_command("install", "BUNDLE_IGNORE_MESSAGES" => "1") if bundle_install?
673
+ if bundle_install?
674
+ bundle_command("install", "BUNDLE_IGNORE_MESSAGES" => "1")
675
+
676
+ # The vast majority of Rails apps will be deployed on `x86_64-linux`.
677
+ platforms = ["--add-platform=x86_64-linux"]
678
+
679
+ # Users that develop on M1 mac may use docker and would need `aarch64-linux` as well.
680
+ platforms << "--add-platform=aarch64-linux" if RUBY_PLATFORM.start_with?("arm64")
681
+
682
+ platforms.each do |platform|
683
+ bundle_command("lock #{platform}", "BUNDLE_IGNORE_MESSAGES" => "1")
684
+ end
685
+ end
446
686
  end
447
687
 
448
688
  def run_javascript
449
689
  return if options[:skip_javascript] || !bundle_install?
450
690
 
451
- case adjusted_javascript_option
452
- when "importmap" then rails_command "importmap:install"
453
- when "webpack", "esbuild", "rollup" then rails_command "javascript:install:#{adjusted_javascript_option}"
691
+ case options[:javascript]
692
+ when "importmap" then rails_command "importmap:install"
693
+ when "webpack", "bun", "esbuild", "rollup" then rails_command "javascript:install:#{options[:javascript]}"
454
694
  end
455
695
  end
456
696
 
457
697
  def run_hotwire
458
- return if options[:skip_javascript] || options[:skip_hotwire] || !bundle_install?
698
+ return if options[:skip_hotwire] || !bundle_install?
459
699
 
460
700
  rails_command "turbo:install stimulus:install"
461
701
  end
@@ -463,8 +703,10 @@ module Rails
463
703
  def run_css
464
704
  return if !options[:css] || !bundle_install?
465
705
 
466
- if !using_node? && options[:css] == "tailwind"
706
+ if !using_js_runtime? && options[:css] == "tailwind"
467
707
  rails_command "tailwindcss:install"
708
+ elsif !using_js_runtime? && options[:css] == "sass"
709
+ rails_command "dartsass:install"
468
710
  else
469
711
  rails_command "css:install:#{options[:css]}"
470
712
  end
@@ -484,6 +726,35 @@ module Rails
484
726
  def keep_file(destination)
485
727
  create_file("#{destination}/.keep") if keeps?
486
728
  end
729
+
730
+ def user_default_branch
731
+ @user_default_branch ||= `git config init.defaultbranch`
732
+ end
733
+
734
+ def git_init_command
735
+ return "git init" if user_default_branch.strip.present?
736
+
737
+ git_version = `git --version`[/\d+\.\d+\.\d+/]
738
+
739
+ if Gem::Version.new(git_version) >= Gem::Version.new("2.28.0")
740
+ "git init -b main"
741
+ else
742
+ "git init && git symbolic-ref HEAD refs/heads/main"
743
+ end
744
+ end
745
+
746
+ def edge_branch
747
+ self.class.edge_branch
748
+ end
749
+
750
+ def dockerfile_chown_directories
751
+ directories = %w(log tmp)
752
+
753
+ directories << "storage" unless skip_active_storage? && !sqlite3?
754
+ directories << "db" unless skip_active_record?
755
+
756
+ directories.sort
757
+ end
487
758
  end
488
759
  end
489
760
  end