railties 7.0.10 → 7.1.0.beta1

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 (162) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +565 -234
  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 +116 -31
  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 +36 -16
  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 +14 -28
  65. data/lib/rails/generators/app_base.rb +353 -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 +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 -1
  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 +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 +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 +6 -14
  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/rack/logger.rb +15 -12
  131. data/lib/rails/rackup/server.rb +15 -0
  132. data/lib/rails/railtie/configuration.rb +14 -1
  133. data/lib/rails/railtie.rb +18 -18
  134. data/lib/rails/ruby_version_check.rb +2 -0
  135. data/lib/rails/source_annotation_extractor.rb +67 -18
  136. data/lib/rails/tasks/engine.rake +8 -8
  137. data/lib/rails/tasks/framework.rake +4 -10
  138. data/lib/rails/tasks/log.rake +1 -1
  139. data/lib/rails/tasks/misc.rake +3 -14
  140. data/lib/rails/tasks/statistics.rake +5 -4
  141. data/lib/rails/tasks/tmp.rake +5 -5
  142. data/lib/rails/tasks/zeitwerk.rake +1 -1
  143. data/lib/rails/tasks.rb +0 -2
  144. data/lib/rails/templates/rails/mailers/email.html.erb +25 -0
  145. data/lib/rails/templates/rails/mailers/index.html.erb +14 -7
  146. data/lib/rails/templates/rails/mailers/mailer.html.erb +11 -5
  147. data/lib/rails/templates/rails/welcome/index.html.erb +1 -0
  148. data/lib/rails/test_help.rb +7 -7
  149. data/lib/rails/test_unit/line_filtering.rb +1 -1
  150. data/lib/rails/test_unit/reporter.rb +6 -2
  151. data/lib/rails/test_unit/runner.rb +36 -18
  152. data/lib/rails/test_unit/test_parser.rb +88 -0
  153. data/lib/rails/test_unit/testing.rake +13 -33
  154. data/lib/rails/version.rb +1 -1
  155. data/lib/rails.rb +15 -15
  156. metadata +69 -31
  157. data/RDOC_MAIN.rdoc +0 -97
  158. data/lib/rails/application/dummy_erb_compiler.rb +0 -18
  159. data/lib/rails/generators/rails/app/templates/config/initializers/new_framework_defaults_7_0.rb.tt +0 -143
  160. data/lib/rails/generators/rails/model/USAGE +0 -113
  161. data/lib/rails/tasks/middleware.rake +0 -9
  162. 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:
@@ -173,7 +270,7 @@ module Rails
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