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