railties 7.0.8.7 → 7.1.5.1

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +723 -215
  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 +14 -2
  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 +141 -33
  15. data/lib/rails/backtrace_cleaner.rb +5 -3
  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 +40 -2
  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 +4 -5
  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 +280 -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 +21 -20
  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 +33 -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 +31 -31
  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 +64 -27
  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