railties 7.0.8 → 7.1.3.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (168) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +692 -207
  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 +23 -4
  10. data/lib/rails/application/configuration.rb +190 -69
  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 +43 -33
  14. data/lib/rails/application.rb +134 -29
  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 +20 -38
  52. data/lib/rails/commands/server/server_command.rb +33 -32
  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 +50 -6
  61. data/lib/rails/engine.rb +49 -21
  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 +28 -14
  65. data/lib/rails/generators/app_base.rb +355 -82
  66. data/lib/rails/generators/app_name.rb +3 -14
  67. data/lib/rails/generators/base.rb +17 -9
  68. data/lib/rails/generators/database.rb +39 -1
  69. data/lib/rails/generators/erb/mailer/templates/layout.html.erb.tt +1 -1
  70. data/lib/rails/generators/generated_attribute.rb +12 -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 +22 -6
  74. data/lib/rails/generators/rails/app/app_generator.rb +85 -64
  75. data/lib/rails/generators/rails/app/templates/Dockerfile.tt +103 -0
  76. data/lib/rails/generators/rails/app/templates/Gemfile.tt +9 -11
  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 +6 -17
  80. data/lib/rails/generators/rails/app/templates/config/databases/jdbcpostgresql.yml.tt +4 -4
  81. data/lib/rails/generators/rails/app/templates/config/databases/jdbcsqlite3.yml.tt +3 -3
  82. data/lib/rails/generators/rails/app/templates/config/databases/postgresql.yml.tt +4 -6
  83. data/lib/rails/generators/rails/app/templates/config/databases/sqlite3.yml.tt +3 -3
  84. data/lib/rails/generators/rails/app/templates/config/databases/trilogy.yml.tt +59 -0
  85. data/lib/rails/generators/rails/app/templates/config/environments/development.rb.tt +12 -2
  86. data/lib/rails/generators/rails/app/templates/config/environments/production.rb.tt +32 -28
  87. data/lib/rails/generators/rails/app/templates/config/environments/test.rb.tt +13 -9
  88. data/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt +2 -0
  89. data/lib/rails/generators/rails/app/templates/config/initializers/content_security_policy.rb.tt +2 -2
  90. data/lib/rails/generators/rails/app/templates/config/initializers/cors.rb.tt +1 -1
  91. data/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt +3 -3
  92. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_1.rb.tt +284 -0
  93. data/lib/rails/generators/rails/app/templates/config/initializers/permissions_policy.rb.tt +11 -9
  94. data/lib/rails/generators/rails/app/templates/config/locales/en.yml +11 -13
  95. data/lib/rails/generators/rails/app/templates/config/puma.rb.tt +11 -19
  96. data/lib/rails/generators/rails/app/templates/config/routes.rb.tt +5 -1
  97. data/lib/rails/generators/rails/app/templates/db/seeds.rb.tt +6 -4
  98. data/lib/rails/generators/rails/app/templates/docker-entrypoint.tt +10 -0
  99. data/lib/rails/generators/rails/app/templates/dockerignore.tt +43 -0
  100. data/lib/rails/generators/rails/app/templates/gitignore.tt +4 -8
  101. data/lib/rails/generators/rails/app/templates/node-version.tt +1 -0
  102. data/lib/rails/generators/rails/app/templates/test/channels/application_cable/connection_test.rb.tt +10 -8
  103. data/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +9 -7
  104. data/lib/rails/generators/rails/application_record/application_record_generator.rb +4 -0
  105. data/lib/rails/generators/rails/benchmark/benchmark_generator.rb +2 -1
  106. data/lib/rails/generators/rails/controller/USAGE +12 -4
  107. data/lib/rails/generators/rails/controller/controller_generator.rb +5 -0
  108. data/lib/rails/generators/rails/controller/templates/controller.rb.tt +1 -1
  109. data/lib/rails/generators/rails/credentials/credentials_generator.rb +29 -24
  110. data/lib/rails/generators/rails/credentials/templates/credentials.yml.tt +8 -0
  111. data/lib/rails/generators/rails/db/system/change/change_generator.rb +30 -0
  112. data/lib/rails/generators/rails/encryption_key_file/encryption_key_file_generator.rb +1 -2
  113. data/lib/rails/generators/rails/migration/USAGE +21 -11
  114. data/lib/rails/generators/rails/model/model_generator.rb +4 -0
  115. data/lib/rails/generators/rails/plugin/USAGE +17 -6
  116. data/lib/rails/generators/rails/plugin/plugin_generator.rb +5 -15
  117. data/lib/rails/generators/rails/plugin/templates/Gemfile.tt +2 -2
  118. data/lib/rails/generators/rails/plugin/templates/MIT-LICENSE.tt +1 -1
  119. data/lib/rails/generators/rails/plugin/templates/bin/rails.tt +1 -17
  120. data/lib/rails/generators/rails/plugin/templates/gitignore.tt +0 -2
  121. data/lib/rails/generators/rails/plugin/templates/test/test_helper.rb.tt +4 -4
  122. data/lib/rails/generators/rails/resource/resource_generator.rb +6 -0
  123. data/lib/rails/generators/rails/scaffold/scaffold_generator.rb +2 -1
  124. data/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb +1 -1
  125. data/lib/rails/generators/test_case.rb +2 -2
  126. data/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb +1 -1
  127. data/lib/rails/generators/testing/{behaviour.rb → behavior.rb} +4 -1
  128. data/lib/rails/generators.rb +6 -14
  129. data/lib/rails/health_controller.rb +55 -0
  130. data/lib/rails/info.rb +1 -1
  131. data/lib/rails/info_controller.rb +31 -11
  132. data/lib/rails/mailers_controller.rb +15 -5
  133. data/lib/rails/paths.rb +13 -10
  134. data/lib/rails/rack/logger.rb +15 -12
  135. data/lib/rails/rackup/server.rb +15 -0
  136. data/lib/rails/railtie/configuration.rb +14 -1
  137. data/lib/rails/railtie.rb +18 -18
  138. data/lib/rails/ruby_version_check.rb +2 -0
  139. data/lib/rails/source_annotation_extractor.rb +67 -18
  140. data/lib/rails/tasks/engine.rake +8 -8
  141. data/lib/rails/tasks/framework.rake +4 -10
  142. data/lib/rails/tasks/log.rake +1 -1
  143. data/lib/rails/tasks/misc.rake +3 -14
  144. data/lib/rails/tasks/statistics.rake +5 -4
  145. data/lib/rails/tasks/tmp.rake +5 -5
  146. data/lib/rails/tasks/zeitwerk.rake +15 -35
  147. data/lib/rails/tasks.rb +0 -2
  148. data/lib/rails/templates/rails/mailers/email.html.erb +32 -0
  149. data/lib/rails/templates/rails/mailers/index.html.erb +14 -7
  150. data/lib/rails/templates/rails/mailers/mailer.html.erb +11 -5
  151. data/lib/rails/templates/rails/welcome/index.html.erb +1 -0
  152. data/lib/rails/test_help.rb +9 -14
  153. data/lib/rails/test_unit/line_filtering.rb +1 -1
  154. data/lib/rails/test_unit/reporter.rb +6 -2
  155. data/lib/rails/test_unit/runner.rb +36 -18
  156. data/lib/rails/test_unit/test_parser.rb +88 -0
  157. data/lib/rails/test_unit/testing.rake +13 -33
  158. data/lib/rails/testing/maintain_test_schema.rb +16 -0
  159. data/lib/rails/version.rb +1 -1
  160. data/lib/rails/zeitwerk_checker.rb +15 -0
  161. data/lib/rails.rb +15 -15
  162. metadata +66 -29
  163. data/RDOC_MAIN.rdoc +0 -97
  164. data/lib/rails/application/dummy_erb_compiler.rb +0 -18
  165. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_0.rb.tt +0 -143
  166. data/lib/rails/generators/rails/model/USAGE +0 -113
  167. data/lib/rails/tasks/middleware.rake +0 -9
  168. 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,43 @@ 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]
346
+ end
347
+
348
+ def skip_action_cable? # :doc:
349
+ options[:skip_action_cable]
224
350
  end
225
351
 
226
352
  def skip_action_mailer? # :doc:
227
- options[:skip_action_mailer] || options[:skip_active_job]
353
+ options[:skip_action_mailer]
228
354
  end
229
355
 
230
356
  def skip_action_mailbox? # :doc:
231
- options[:skip_action_mailbox] || skip_active_storage?
357
+ options[:skip_action_mailbox]
232
358
  end
233
359
 
234
360
  def skip_action_text? # :doc:
235
- options[:skip_action_text] || skip_active_storage?
361
+ options[:skip_action_text]
236
362
  end
237
363
 
238
- def skip_dev_gems? # :doc:
239
- options[:skip_dev_gems]
364
+ def skip_asset_pipeline? # :doc:
365
+ options[:skip_asset_pipeline]
240
366
  end
241
367
 
242
368
  def skip_sprockets?
243
- options[:skip_asset_pipeline] || options[:asset_pipeline] != "sprockets"
369
+ skip_asset_pipeline? || options[:asset_pipeline] != "sprockets"
244
370
  end
245
371
 
246
372
  def skip_propshaft?
247
- options[:skip_asset_pipeline] || options[:asset_pipeline] != "propshaft"
373
+ skip_asset_pipeline? || options[:asset_pipeline] != "propshaft"
248
374
  end
249
375
 
250
376
 
@@ -284,6 +410,10 @@ module Rails
284
410
  end
285
411
  end
286
412
 
413
+ def gem_ruby_version
414
+ Gem::Version.new(Gem::VERSION) >= Gem::Version.new("3.3.13") ? Gem.ruby_version : RUBY_VERSION
415
+ end
416
+
287
417
  def rails_prerelease?
288
418
  options.dev? || options.edge? || options.main?
289
419
  end
@@ -292,7 +422,6 @@ module Rails
292
422
  if options.dev?
293
423
  GemfileEntry.path("rails", Rails::Generators::RAILS_DEV_PATH, "Use local checkout of Rails")
294
424
  elsif options.edge?
295
- edge_branch = Rails.gem_version.prerelease? ? "main" : [*Rails.gem_version.segments.first(2), "stable"].join("-")
296
425
  GemfileEntry.github("rails", "rails/rails", edge_branch, "Use specific branch of Rails")
297
426
  elsif options.main?
298
427
  GemfileEntry.github("rails", "rails/rails", "main", "Use main development branch of Rails")
@@ -323,7 +452,7 @@ module Rails
323
452
  def javascript_gemfile_entry
324
453
  return if options[:skip_javascript]
325
454
 
326
- if adjusted_javascript_option == "importmap"
455
+ if options[:javascript] == "importmap"
327
456
  GemfileEntry.floats "importmap-rails", "Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails]"
328
457
  else
329
458
  GemfileEntry.floats "jsbundling-rails", "Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails]"
@@ -331,7 +460,7 @@ module Rails
331
460
  end
332
461
 
333
462
  def hotwire_gemfile_entry
334
- return if options[:skip_javascript] || options[:skip_hotwire]
463
+ return if options[:skip_hotwire]
335
464
 
336
465
  turbo_rails_entry =
337
466
  GemfileEntry.floats "turbo-rails", "Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev]"
@@ -342,43 +471,141 @@ module Rails
342
471
  [ turbo_rails_entry, stimulus_rails_entry ]
343
472
  end
344
473
 
474
+ def using_js_runtime?
475
+ (options[:javascript] && !%w[importmap].include?(options[:javascript])) ||
476
+ (options[:css] && !%w[tailwind sass].include?(options[:css]))
477
+ end
478
+
345
479
  def using_node?
346
- options[:javascript] && options[:javascript] != "importmap"
480
+ using_js_runtime? && !%w[bun].include?(options[:javascript])
347
481
  end
348
482
 
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]
483
+ def using_bun?
484
+ using_js_runtime? && %w[bun].include?(options[:javascript])
485
+ end
486
+
487
+ def node_version
488
+ if using_node?
489
+ ENV.fetch("NODE_VERSION") do
490
+ `node --version`[/\d+\.\d+\.\d+/]
491
+ rescue
492
+ NODE_LTS_VERSION
493
+ end
356
494
  end
357
495
  end
358
496
 
497
+ def dockerfile_yarn_version
498
+ using_node? and `yarn --version`[/\d+\.\d+\.\d+/]
499
+ rescue
500
+ "latest"
501
+ end
502
+
503
+ def dockerfile_bun_version
504
+ using_bun? and `bun --version`[/\d+\.\d+\.\d+/]
505
+ rescue
506
+ BUN_VERSION
507
+ end
508
+
509
+ def dockerfile_binfile_fixups
510
+ # binfiles may have OS specific paths to ruby. Normalize them.
511
+ shebangs = Dir["bin/*"].map { |file| IO.read(file).lines.first }.join
512
+ rubies = shebangs.scan(%r{#!/usr/bin/env (ruby.*)}).flatten.uniq
513
+
514
+ binfixups = (rubies - %w(ruby)).map do |ruby|
515
+ "sed -i 's/#{Regexp.quote(ruby)}$/ruby/' bin/*"
516
+ end
517
+
518
+ # Windows line endings will cause scripts to fail. If any
519
+ # or found OR this generation is run on a windows platform
520
+ # and there are other binfixups required, then convert
521
+ # line endings. This avoids adding unnecessary fixups if
522
+ # none are required, but prepares for the need to do the
523
+ # fix line endings if other fixups are required.
524
+ has_cr = Dir["bin/*"].any? { |file| IO.read(file).include? "\r" }
525
+ if has_cr || (Gem.win_platform? && !binfixups.empty?)
526
+ binfixups.unshift 'sed -i "s/\r$//g" bin/*'
527
+ end
528
+
529
+ # Windows file systems may not have the concept of executable.
530
+ # In such cases, fix up during the build.
531
+ unless Dir["bin/*"].all? { |file| File.executable? file }
532
+ binfixups.unshift "chmod +x bin/*"
533
+ end
534
+
535
+ binfixups
536
+ end
537
+
538
+ def dockerfile_build_packages
539
+ # start with the essentials
540
+ packages = %w(build-essential git pkg-config)
541
+
542
+ # add database support
543
+ packages << build_package_for_database unless skip_active_record?
544
+
545
+ # ActiveStorage preview support
546
+ packages << "libvips" unless skip_active_storage?
547
+
548
+ packages << "curl" if using_js_runtime?
549
+
550
+ packages << "unzip" if using_bun?
551
+
552
+ # node support, including support for building native modules
553
+ if using_node?
554
+ packages << "node-gyp" # pkg-config already listed above
555
+
556
+ # module build process depends on Python, and debian changed
557
+ # how python is installed with the bullseye release. Below
558
+ # is based on debian release included with the Ruby images on
559
+ # Dockerhub.
560
+ case Gem.ruby_version.to_s
561
+ when /^2\.7/
562
+ bullseye = Gem.ruby_version >= Gem::Version.new("2.7.4")
563
+ when /^3\.0/
564
+ bullseye = Gem.ruby_version >= Gem::Version.new("3.0.2")
565
+ else
566
+ bullseye = true
567
+ end
568
+
569
+ if bullseye
570
+ packages << "python-is-python3"
571
+ else
572
+ packages << "python"
573
+ end
574
+ end
575
+
576
+ packages.compact.sort
577
+ end
578
+
579
+ def dockerfile_deploy_packages
580
+ # Add curl to work with the default healthcheck strategy in Kamal
581
+ packages = ["curl"]
582
+
583
+ # ActiveRecord databases
584
+ packages << deploy_package_for_database unless skip_active_record?
585
+
586
+ # ActiveStorage preview support
587
+ packages << "libvips" unless skip_active_storage?
588
+
589
+ packages.compact.sort
590
+ end
591
+
359
592
  def css_gemfile_entry
360
593
  return unless options[:css]
361
594
 
362
- if !using_node? && options[:css] == "tailwind"
595
+ if !using_js_runtime? && options[:css] == "tailwind"
363
596
  GemfileEntry.floats "tailwindcss-rails", "Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]"
597
+ elsif !using_js_runtime? && options[:css] == "sass"
598
+ GemfileEntry.floats "dartsass-rails", "Use Dart SASS [https://github.com/rails/dartsass-rails]"
364
599
  else
365
600
  GemfileEntry.floats "cssbundling-rails", "Bundle and process CSS [https://github.com/rails/cssbundling-rails]"
366
601
  end
367
602
  end
368
603
 
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
604
  def cable_gemfile_entry
378
605
  return if options[:skip_action_cable]
379
606
 
380
607
  comment = "Use Redis adapter to run Action Cable in production"
381
- GemfileEntry.new("redis", "~> 4.0", comment, {}, true)
608
+ GemfileEntry.new("redis", ">= 4.0.1", comment, {}, true)
382
609
  end
383
610
 
384
611
  def bundle_command(command, env = {})
@@ -411,6 +638,10 @@ module Rails
411
638
  !(options[:skip_bundle] || options[:pretend])
412
639
  end
413
640
 
641
+ def bundler_windows_platforms
642
+ Gem.rubygems_version >= Gem::Version.new("3.3.22") ? "windows" : "mswin mswin64 mingw x64_mingw"
643
+ end
644
+
414
645
  def depends_on_system_test?
415
646
  !(options[:skip_system_test] || options[:skip_test] || options[:api])
416
647
  end
@@ -431,7 +662,8 @@ module Rails
431
662
 
432
663
  run_bundle
433
664
 
434
- @argv[0] = destination_root
665
+ @argv.delete_at(@argv.index(app_path))
666
+ @argv.unshift(destination_root)
435
667
  require "shellwords"
436
668
  bundle_command("exec rails #{self_command} #{Shellwords.join(@argv)}")
437
669
  exit
@@ -448,14 +680,14 @@ module Rails
448
680
  def run_javascript
449
681
  return if options[:skip_javascript] || !bundle_install?
450
682
 
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}"
683
+ case options[:javascript]
684
+ when "importmap" then rails_command "importmap:install"
685
+ when "webpack", "bun", "esbuild", "rollup" then rails_command "javascript:install:#{options[:javascript]}"
454
686
  end
455
687
  end
456
688
 
457
689
  def run_hotwire
458
- return if options[:skip_javascript] || options[:skip_hotwire] || !bundle_install?
690
+ return if options[:skip_hotwire] || !bundle_install?
459
691
 
460
692
  rails_command "turbo:install stimulus:install"
461
693
  end
@@ -463,13 +695,25 @@ module Rails
463
695
  def run_css
464
696
  return if !options[:css] || !bundle_install?
465
697
 
466
- if !using_node? && options[:css] == "tailwind"
698
+ if !using_js_runtime? && options[:css] == "tailwind"
467
699
  rails_command "tailwindcss:install"
700
+ elsif !using_js_runtime? && options[:css] == "sass"
701
+ rails_command "dartsass:install"
468
702
  else
469
703
  rails_command "css:install:#{options[:css]}"
470
704
  end
471
705
  end
472
706
 
707
+ def add_bundler_platforms
708
+ if bundle_install?
709
+ # The vast majority of Rails apps will be deployed on `x86_64-linux`.
710
+ bundle_command("lock --add-platform=x86_64-linux")
711
+
712
+ # Users that develop on M1 mac may use docker and would need `aarch64-linux` as well.
713
+ bundle_command("lock --add-platform=aarch64-linux") if RUBY_PLATFORM.start_with?("arm64")
714
+ end
715
+ end
716
+
473
717
  def generate_bundler_binstub
474
718
  if bundle_install?
475
719
  bundle_command("binstubs bundler")
@@ -484,6 +728,35 @@ module Rails
484
728
  def keep_file(destination)
485
729
  create_file("#{destination}/.keep") if keeps?
486
730
  end
731
+
732
+ def user_default_branch
733
+ @user_default_branch ||= `git config init.defaultbranch`
734
+ end
735
+
736
+ def git_init_command
737
+ return "git init" if user_default_branch.strip.present?
738
+
739
+ git_version = `git --version`[/\d+\.\d+\.\d+/]
740
+
741
+ if Gem::Version.new(git_version) >= Gem::Version.new("2.28.0")
742
+ "git init -b main"
743
+ else
744
+ "git init && git symbolic-ref HEAD refs/heads/main"
745
+ end
746
+ end
747
+
748
+ def edge_branch
749
+ self.class.edge_branch
750
+ end
751
+
752
+ def dockerfile_chown_directories
753
+ directories = %w(log tmp)
754
+
755
+ directories << "storage" unless skip_active_storage? && !sqlite3?
756
+ directories << "db" unless skip_active_record?
757
+
758
+ directories.sort
759
+ end
487
760
  end
488
761
  end
489
762
  end