hanami-cli 2.3.5 → 3.0.0.rc1

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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +38 -5
  3. data/README.md +6 -1
  4. data/hanami-cli.gemspec +2 -3
  5. data/lib/hanami/cli/commands/app/db/migrate.rb +4 -4
  6. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +4 -6
  7. data/lib/hanami/cli/commands/app/generate/action.rb +31 -7
  8. data/lib/hanami/cli/commands/app/generate/component.rb +2 -0
  9. data/lib/hanami/cli/commands/app/generate/mailer.rb +52 -0
  10. data/lib/hanami/cli/commands/app/generate/operation.rb +6 -0
  11. data/lib/hanami/cli/commands/app/generate/part.rb +4 -8
  12. data/lib/hanami/cli/commands/app/generate/provider.rb +27 -0
  13. data/lib/hanami/cli/commands/app/generate/relation.rb +7 -3
  14. data/lib/hanami/cli/commands/app/generate/repo.rb +3 -1
  15. data/lib/hanami/cli/commands/app/generate/slice.rb +16 -10
  16. data/lib/hanami/cli/commands/app/generate/struct.rb +2 -0
  17. data/lib/hanami/cli/commands/app/generate/view.rb +24 -1
  18. data/lib/hanami/cli/commands/app/middleware.rb +3 -3
  19. data/lib/hanami/cli/commands/app/routes.rb +5 -5
  20. data/lib/hanami/cli/commands/app/server.rb +2 -2
  21. data/lib/hanami/cli/commands/app.rb +6 -1
  22. data/lib/hanami/cli/commands/gem/new.rb +63 -82
  23. data/lib/hanami/cli/errors.rb +1 -1
  24. data/lib/hanami/cli/files.rb +9 -2
  25. data/lib/hanami/cli/generators/app/action.rb +39 -21
  26. data/lib/hanami/cli/generators/app/component.rb +2 -2
  27. data/lib/hanami/cli/generators/app/mailer.rb +81 -0
  28. data/lib/hanami/cli/generators/app/migration.rb +1 -1
  29. data/lib/hanami/cli/generators/app/operation.rb +2 -2
  30. data/lib/hanami/cli/generators/app/part.rb +4 -4
  31. data/lib/hanami/cli/generators/app/provider.rb +54 -0
  32. data/lib/hanami/cli/generators/app/relation.rb +2 -2
  33. data/lib/hanami/cli/generators/app/repo.rb +2 -2
  34. data/lib/hanami/cli/generators/app/ruby_file.rb +8 -4
  35. data/lib/hanami/cli/generators/app/slice.rb +37 -14
  36. data/lib/hanami/cli/generators/app/struct.rb +2 -2
  37. data/lib/hanami/cli/generators/app/view.rb +20 -10
  38. data/lib/hanami/cli/generators/context.rb +32 -52
  39. data/lib/hanami/cli/generators/gem/app/app.erb +3 -0
  40. data/lib/hanami/cli/generators/gem/app/app_layout.haml.erb +15 -0
  41. data/lib/hanami/cli/generators/gem/app/app_layout.slim.erb +15 -0
  42. data/lib/hanami/cli/generators/gem/app/assets.js +1 -1
  43. data/lib/hanami/cli/generators/gem/app/context.erb +1 -1
  44. data/lib/hanami/cli/generators/gem/app/env.erb +18 -1
  45. data/lib/hanami/cli/generators/gem/app/gemfile.erb +50 -6
  46. data/lib/hanami/cli/generators/gem/app/package.json.erb +1 -0
  47. data/lib/hanami/cli/generators/gem/app/puma.erb +0 -3
  48. data/lib/hanami/cli/generators/gem/app/readme.erb +2 -2
  49. data/lib/hanami/cli/generators/gem/app/routes.erb +1 -1
  50. data/lib/hanami/cli/generators/gem/app.rb +26 -1
  51. data/lib/hanami/cli/generators/i18n.yml +3 -0
  52. data/lib/hanami/cli/version.rb +1 -1
  53. metadata +11 -5
@@ -7,105 +7,78 @@ module Hanami
7
7
  module CLI
8
8
  module Commands
9
9
  module Gem
10
- # @since 2.0.0
11
10
  # @api private
12
11
  class New < Command
13
- # @since 2.1.0
14
- # @api private
15
- HEAD_DEFAULT = false
16
- private_constant :HEAD_DEFAULT
12
+ FORBIDDEN_APP_NAMES = %w[app slice].freeze
17
13
 
18
- # @since 2.3.0
19
- # @api private
14
+ HEAD_DEFAULT = false
20
15
  GEM_SOURCE_DEFAULT = "rubygems.org"
21
- private_constant :GEM_SOURCE_DEFAULT
22
16
 
23
- # @since 2.0.0
24
- # @api private
25
17
  SKIP_INSTALL_DEFAULT = false
26
- private_constant :SKIP_INSTALL_DEFAULT
27
-
28
- # @since 2.1.0
29
- # @api private
30
18
  SKIP_ASSETS_DEFAULT = false
31
- private_constant :SKIP_ASSETS_DEFAULT
32
-
33
- # @since 2.2.0
34
- # @api private
35
19
  SKIP_DB_DEFAULT = false
36
- private_constant :SKIP_DB_DEFAULT
37
-
38
- # @since 2.2.0
39
- # @api private
40
20
  SKIP_VIEW_DEFAULT = false
41
- private_constant :SKIP_VIEW_DEFAULT
21
+ SKIP_MAILER_DEFAULT = false
42
22
 
43
- # @since 2.2.0
44
- # @api private
45
23
  DATABASE_SQLITE = "sqlite"
46
-
47
- # @since 2.2.0
48
- # @api private
49
24
  DATABASE_POSTGRES = "postgres"
50
-
51
- # @since 2.2.0
52
- # @api private
53
25
  DATABASE_MYSQL = "mysql"
54
-
55
- # @since 2.2.0
56
- # @api private
57
26
  SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze
58
27
 
59
- # @api private
60
- FORBIDDEN_APP_NAMES = %w[app slice].freeze
28
+ TEMPLATE_ENGINE_DEFAULT = "erb"
29
+
30
+ TEST_FRAMEWORK_DEFAULT = "rspec"
31
+ TEST_FRAMEWORK_RSPEC = "rspec"
32
+ TEST_FRAMEWORK_MINITEST = "minitest"
61
33
 
62
34
  desc "Generate a new Hanami app"
63
35
 
64
- # @since 2.0.0
65
- # @api private
66
36
  argument :app, required: true, desc: "App name"
67
37
 
68
- # @since 2.1.0
69
- # @api private
70
38
  option :head, type: :flag, required: false,
71
- default: HEAD_DEFAULT,
72
- desc: "Use Hanami HEAD version (from GitHub `main` branches)"
39
+ default: HEAD_DEFAULT,
40
+ desc: "Use Hanami HEAD version (from GitHub `main` branches)"
73
41
 
74
- # @since 2.3.0
75
- # @api private
76
42
  option :gem_source, required: true,
77
- default: GEM_SOURCE_DEFAULT,
78
- desc: "Where to source Ruby gems from"
43
+ default: GEM_SOURCE_DEFAULT,
44
+ desc: "Where to source Ruby gems from"
79
45
 
80
- # @since 2.0.0
81
- # @api private
82
46
  option :skip_install, type: :flag, required: false,
83
- default: SKIP_INSTALL_DEFAULT,
84
- desc: "Skip app installation (Bundler, third-party Hanami plugins)"
47
+ default: SKIP_INSTALL_DEFAULT,
48
+ desc: "Skip app installation (Bundler, third-party Hanami plugins)"
85
49
 
86
- # @since 2.1.0
87
- # @api private
88
50
  option :skip_assets, type: :flag, required: false,
89
- default: SKIP_ASSETS_DEFAULT,
90
- desc: "Skip including hanami-assets"
51
+ default: SKIP_ASSETS_DEFAULT,
52
+ desc: "Skip including hanami-assets"
91
53
 
92
- # @since 2.2.0
93
- # @api private
94
54
  option :skip_db, type: :flag, required: false,
95
- default: SKIP_DB_DEFAULT,
96
- desc: "Skip including hanami-db"
55
+ default: SKIP_DB_DEFAULT,
56
+ desc: "Skip including hanami-db"
97
57
 
98
- # @since 2.2.0
99
- # @api private
100
58
  option :skip_view, type: :flag, required: false,
101
- default: SKIP_VIEW_DEFAULT,
102
- desc: "Skip including hanami-view"
59
+ default: SKIP_VIEW_DEFAULT,
60
+ desc: "Skip including hanami-view"
61
+
62
+ option :skip_mailer, type: :flag, required: false,
63
+ default: SKIP_MAILER_DEFAULT,
64
+ desc: "Skip including hanami-mailer"
103
65
 
104
- # @since 2.2.0
105
- # @api private
106
66
  option :database, type: :string, required: false,
107
- default: DATABASE_SQLITE,
108
- desc: "Database adapter (supported: sqlite, mysql, postgres)"
67
+ default: DATABASE_SQLITE,
68
+ desc: "Database adapter (supported: sqlite, mysql, postgres)"
69
+
70
+ option :name, type: :string, required: false,
71
+ desc: "App name to use for the module namespace"
72
+
73
+ option :template_engine, type: :string, required: false,
74
+ values: %w[erb haml slim],
75
+ default: TEMPLATE_ENGINE_DEFAULT,
76
+ desc: "Default template engine to use with generators"
77
+
78
+ option :test, type: :string, required: false,
79
+ values: %w[rspec minitest],
80
+ default: TEST_FRAMEWORK_DEFAULT,
81
+ desc: "Test framework (supported: rspec, minitest)"
109
82
 
110
83
  # rubocop:disable Layout/LineLength
111
84
  example [
@@ -116,12 +89,14 @@ module Hanami
116
89
  "bookshelf --skip-assets # Generate a new Hanami app without hanami-assets",
117
90
  "bookshelf --skip-db # Generate a new Hanami app without hanami-db",
118
91
  "bookshelf --skip-view # Generate a new Hanami app without hanami-view",
119
- "bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)"
92
+ "bookshelf --skip-mailer # Generate a new Hanami app without hanami-mailer",
93
+ "bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
94
+ "bookshelf --template-engine={erb|haml|slim} # Generate a new Hanami app which will use HAML for templates by default (default: erb)",
95
+ "bookshelf --test={rspec|minitest} # Generate a new Hanami app with specified test framework (default: rspec)",
96
+ "my_bookshelf --name=bookshelf # Generate a new Hanami app in `my_bookshelf/' directory, using `Bookshelf' namespace"
120
97
  ]
121
98
  # rubocop:enable Layout/LineLength
122
99
 
123
- # @since 2.0.0
124
- # @api private
125
100
  def initialize(
126
101
  fs:,
127
102
  bundler: CLI::Bundler.new(fs: fs),
@@ -137,8 +112,6 @@ module Hanami
137
112
 
138
113
  # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
139
114
 
140
- # @since 2.0.0
141
- # @api private
142
115
  def call(
143
116
  app:,
144
117
  head: HEAD_DEFAULT,
@@ -147,26 +120,34 @@ module Hanami
147
120
  skip_assets: SKIP_ASSETS_DEFAULT,
148
121
  skip_db: SKIP_DB_DEFAULT,
149
122
  skip_view: SKIP_VIEW_DEFAULT,
150
- database: nil
123
+ skip_mailer: SKIP_MAILER_DEFAULT,
124
+ database: nil,
125
+ name: nil,
126
+ template_engine: TEMPLATE_ENGINE_DEFAULT,
127
+ test: TEST_FRAMEWORK_DEFAULT
151
128
  )
152
- app = inflector.underscore(app)
129
+ directory = inflector.underscore(app)
130
+ app = inflector.underscore(name || app)
153
131
 
154
- raise PathAlreadyExistsError.new(app) if fs.exist?(app)
132
+ raise PathAlreadyExistsError.new(directory) if fs.exist?(directory)
155
133
  raise ForbiddenAppNameError.new(app) if FORBIDDEN_APP_NAMES.include?(app)
156
134
 
157
135
  normalized_database ||= normalize_database(database)
158
136
 
159
- fs.mkdir(app)
160
- fs.chdir(app) do
137
+ fs.mkdir(directory)
138
+ fs.chdir(directory) do
161
139
  context = Generators::Context.new(
162
140
  inflector,
163
141
  app,
164
- head: head,
165
- gem_source: gem_source,
166
- skip_assets: skip_assets,
167
- skip_db: skip_db,
168
- skip_view: skip_view,
169
- database: normalized_database
142
+ head:,
143
+ gem_source:,
144
+ skip_assets:,
145
+ skip_db:,
146
+ skip_view:,
147
+ skip_mailer:,
148
+ database: normalized_database,
149
+ template_engine:,
150
+ test_framework: test
170
151
  )
171
152
  generator.call(app, context: context) do
172
153
  if skip_install
@@ -90,7 +90,7 @@ module Hanami
90
90
  # @api public
91
91
  class InvalidActionNameError < Error
92
92
  def initialize(name)
93
- super("cannot parse controller and action name: `#{name}'\n\texample: `hanami generate action users.show'")
93
+ super("cannot parse action name: `#{name}'\n\texample: `hanami generate action users.show'")
94
94
  end
95
95
  end
96
96
 
@@ -15,8 +15,8 @@ module Hanami
15
15
  end
16
16
 
17
17
  # @api private
18
- def create(path, *content)
19
- raise FileAlreadyExistsError.new(path) if exist?(path)
18
+ def create(path, *content, force: false)
19
+ raise FileAlreadyExistsError.new(path) if exist?(path) and !force
20
20
 
21
21
  write(path, *content)
22
22
  end
@@ -60,6 +60,13 @@ module Hanami
60
60
  created(path)
61
61
  end
62
62
 
63
+ def block_contains?(path, target, contents)
64
+ content = adapter.read(path)
65
+ class_start = content.index(target)
66
+ class_end = content.index(/(?:^|\s)#{CLOSE_BLOCK}(?:$|\s)/, class_start)
67
+ content[class_start..class_end].include? contents
68
+ end
69
+
63
70
  private
64
71
 
65
72
  attr_reader :out
@@ -11,6 +11,9 @@ module Hanami
11
11
  # @since 2.0.0
12
12
  # @api private
13
13
  class Action
14
+ DEFAULT_TEMPLATE_ENGINE = "erb"
15
+ private_constant :DEFAULT_TEMPLATE_ENGINE
16
+
14
17
  # @since 2.0.0
15
18
  # @api private
16
19
  def initialize(fs:, inflector:, out: $stdout)
@@ -26,12 +29,14 @@ module Hanami
26
29
 
27
30
  # @since 2.0.0
28
31
  # @api private
29
- def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip_route:, skip_tests:)
32
+ def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip_route:, skip_tests:,
33
+ force:, template_engine: DEFAULT_TEMPLATE_ENGINE)
30
34
  insert_route(key:, namespace:, url_path:, http_method:) unless skip_route
31
35
 
32
- generate_action(key: key, namespace: namespace, base_path: base_path, include_placeholder_body: skip_view)
36
+ generate_action(key: key, namespace: namespace, base_path: base_path, include_placeholder_body: skip_view,
37
+ force:)
33
38
 
34
- generate_view(key:, namespace:, base_path:) unless skip_view
39
+ generate_view(key:, namespace:, base_path:, template_engine:, force:) unless skip_view
35
40
  end
36
41
 
37
42
  private
@@ -80,22 +85,21 @@ module Hanami
80
85
  route = route_definition(key:, url_path:, http_method:)
81
86
 
82
87
  if namespace == Hanami.app.namespace
83
- fs.inject_line_at_class_bottom(routes_location, "class Routes", route)
88
+ add_route_to_file(file: routes_location, route:)
84
89
  else
85
90
  slice_routes = fs.join("slices", namespace, "config", "routes.rb")
86
91
 
87
92
  if fs.exist?(slice_routes)
88
- fs.inject_line_at_class_bottom(slice_routes, "class Routes", route)
93
+ add_route_to_file(file: slice_routes, route:)
89
94
  else
90
- slice_matcher = /slice[[:space:]]*:#{namespace}/
91
- fs.inject_line_at_block_bottom(routes_location, slice_matcher, route)
95
+ add_route_to_block(file: routes_location, namespace:, route:)
92
96
  end
93
97
  end
94
98
  end
95
99
 
96
100
  # @api private
97
101
  # @since 2.2.2
98
- def generate_action(key:, namespace:, base_path:, include_placeholder_body:)
102
+ def generate_action(key:, namespace:, base_path:, include_placeholder_body:, force:)
99
103
  RubyClassFile.new(
100
104
  fs: fs,
101
105
  inflector: inflector,
@@ -109,32 +113,28 @@ module Hanami
109
113
  (" response.body = self.class.name" if include_placeholder_body),
110
114
  "end"
111
115
  ].compact
112
- ).create
116
+ ).create(force:)
113
117
  end
114
118
 
115
119
  # @api private
116
120
  # @since 2.2.2
117
- def generate_view(key:, namespace:, base_path:)
118
- *controller_name_parts, action_name = key.split(KEY_SEPARATOR)
121
+ def generate_view(key:, namespace:, base_path:, template_engine:, force:)
122
+ *namespace_parts, action_name = key.split(KEY_SEPARATOR)
119
123
 
120
- view_directory = fs.join(base_path, "views", controller_name_parts)
124
+ view_directory = fs.join(base_path, "views", namespace_parts)
121
125
 
122
126
  if generate_view?(action_name, view_directory)
123
- view_generator.call(
124
- key: key,
125
- namespace: namespace,
126
- base_path: base_path
127
- )
127
+ view_generator.call(key:, namespace:, base_path:, template_engine:, force:)
128
128
  end
129
129
  end
130
130
 
131
131
  # @api private
132
132
  # @since 2.2.2
133
133
  def route_definition(key:, url_path:, http_method:)
134
- *controller_name_parts, action_name = key.split(KEY_SEPARATOR)
134
+ *namespace_parts, action_name = key.split(KEY_SEPARATOR)
135
135
 
136
136
  method = route_http(action_name, http_method)
137
- path = route_url(controller_name_parts, action_name, url_path)
137
+ path = route_url(namespace_parts, action_name, url_path)
138
138
 
139
139
  %(#{method} "#{path}", to: "#{key}")
140
140
  end
@@ -171,9 +171,9 @@ module Hanami
171
171
 
172
172
  # @api private
173
173
  # @since 2.1.0
174
- def route_url(controller, action, url_path)
174
+ def route_url(namespace, action, url_path)
175
175
  action = ROUTE_RESTFUL_URL_SUFFIXES.fetch(action) { [action] }
176
- url_path ||= "#{PATH_SEPARATOR}#{(controller + action).join(PATH_SEPARATOR)}"
176
+ url_path ||= "#{PATH_SEPARATOR}#{(namespace + action).join(PATH_SEPARATOR)}"
177
177
 
178
178
  CLI::URL.call(url_path)
179
179
  end
@@ -189,6 +189,24 @@ module Hanami
189
189
 
190
190
  result
191
191
  end
192
+
193
+ def add_route_to_file(file:, route:)
194
+ target_class = "class Routes"
195
+ if fs.block_contains?(file, target_class, route)
196
+ out.puts "Route (#{route}) already exists, skipping..."
197
+ else
198
+ fs.inject_line_at_class_bottom(file, "class Routes", route)
199
+ end
200
+ end
201
+
202
+ def add_route_to_block(file:, namespace:, route:)
203
+ slice_matcher = /slice[[:space:]]*:#{namespace}/
204
+ if fs.block_contains?(file, slice_matcher, route)
205
+ out.puts "Route (#{route}) already exists, skipping..."
206
+ else
207
+ fs.inject_line_at_block_bottom(file, slice_matcher, route)
208
+ end
209
+ end
192
210
  end
193
211
  end
194
212
  end
@@ -20,14 +20,14 @@ module Hanami
20
20
 
21
21
  # @api private
22
22
  # @since 2.2.0
23
- def call(key:, namespace:, base_path:)
23
+ def call(key:, namespace:, base_path:, force: false)
24
24
  RubyClassFile.new(
25
25
  fs: fs,
26
26
  inflector: inflector,
27
27
  key: key,
28
28
  namespace: namespace,
29
29
  base_path: base_path
30
- ).create
30
+ ).create(force:)
31
31
  end
32
32
 
33
33
  private
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/files"
4
+ require_relative "../constants"
5
+ require_relative "../../errors"
6
+
7
+ module Hanami
8
+ module CLI
9
+ module Generators
10
+ module App
11
+ # @api private
12
+ class Mailer
13
+ TEMPLATES_FOLDER = "templates/mailers"
14
+ DEFAULT_TEMPLATE_ENGINE = "erb"
15
+
16
+ def initialize(fs:, inflector:, out: $stdout)
17
+ @fs = fs
18
+ @inflector = inflector
19
+ @out = out
20
+ end
21
+
22
+ def call(key:, namespace:, base_path:, template_engine: DEFAULT_TEMPLATE_ENGINE, force: false, **)
23
+ mailer_class_file(key:, namespace:, base_path:).then do |mailer_class|
24
+ mailer_class.create(force:)
25
+ mailer_class_name = mailer_class.fully_qualified_name
26
+ create_template_files(key:, base_path:, mailer_class_name:, template_engine:, force:)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :fs, :inflector, :out
33
+
34
+ def mailer_class_file(key:, namespace:, base_path:)
35
+ RubyClassFile.new(
36
+ fs:, inflector:,
37
+ namespace:,
38
+ key:,
39
+ base_path:,
40
+ parent_class_name: "#{inflector.camelize(namespace)}::Mailer",
41
+ extra_namespace: "Mailers"
42
+ )
43
+ end
44
+
45
+ def create_template_files(key:, base_path:, mailer_class_name:, template_engine:, force:)
46
+ key_parts = key.split(KEY_SEPARATOR)
47
+ class_name_from_key = key_parts.pop # takes last segment as the class name
48
+ module_names_from_key = key_parts # the rest of the segments are the module names
49
+
50
+ html_path = template_path(base_path, module_names_from_key, class_name_from_key, "html", template_engine)
51
+ fs.create(html_path, html_body(mailer_class_name, template_engine), force:)
52
+
53
+ text_path = template_path(base_path, module_names_from_key, class_name_from_key, "text", "erb")
54
+ fs.create(text_path, text_body(mailer_class_name), force:)
55
+ end
56
+
57
+ def template_path(base_path, module_names, name, format, engine)
58
+ fs.join(
59
+ base_path,
60
+ TEMPLATES_FOLDER,
61
+ module_names,
62
+ "#{name}.#{format}.#{engine}"
63
+ )
64
+ end
65
+
66
+ def html_body(mailer_class_name, engine)
67
+ case engine
68
+ when "erb" then "<h1>#{mailer_class_name}</h1>\n"
69
+ when "haml" then "%h1 #{mailer_class_name}\n"
70
+ when "slim" then "h1 #{mailer_class_name}\n"
71
+ end
72
+ end
73
+
74
+ def text_body(mailer_class_name)
75
+ "#{mailer_class_name}\n"
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -55,7 +55,7 @@ module Hanami
55
55
  ROM::SQL.migration do
56
56
  # Add your migration here.
57
57
  #
58
- # See https://guides.hanamirb.org/v2.3/database/migrations/ for details.
58
+ # See https://hanakai.org/learn/hanami/database/migrations/ for details.
59
59
  change do
60
60
  end
61
61
  end
@@ -19,7 +19,7 @@ module Hanami
19
19
 
20
20
  # @since 2.2.0
21
21
  # @api private
22
- def call(key:, namespace:, base_path:)
22
+ def call(key:, namespace:, base_path:, force: false, **)
23
23
  RubyClassFile.new(
24
24
  fs: fs,
25
25
  inflector: inflector,
@@ -28,7 +28,7 @@ module Hanami
28
28
  base_path: base_path,
29
29
  parent_class_name: "#{inflector.camelize(namespace)}::Operation",
30
30
  body: ["def call", "end"]
31
- ).create
31
+ ).create(force:)
32
32
 
33
33
  unless key.match?(KEY_SEPARATOR)
34
34
  out.puts(
@@ -20,10 +20,10 @@ module Hanami
20
20
 
21
21
  # @since 2.1.0
22
22
  # @api private
23
- def call(key:, namespace:, base_path:, **)
23
+ def call(key:, namespace:, base_path:, force: false, **)
24
24
  create_app_base_part_if_missing(key:, namespace:, base_path:)
25
25
  create_slice_part_if_missing(key:, namespace:, base_path:) unless namespace == Hanami.app.namespace
26
- create_generated_part(key:, namespace:, base_path:)
26
+ create_generated_part(key:, namespace:, base_path:, force:)
27
27
  end
28
28
 
29
29
  private
@@ -60,7 +60,7 @@ module Hanami
60
60
  ).create
61
61
  end
62
62
 
63
- def create_generated_part(key:, namespace:, base_path:)
63
+ def create_generated_part(key:, namespace:, base_path:, force: false)
64
64
  RubyClassFile.new(
65
65
  fs: fs,
66
66
  inflector: inflector,
@@ -69,7 +69,7 @@ module Hanami
69
69
  base_path: base_path,
70
70
  parent_class_name: "#{inflector.camelize(namespace)}::Views::Part",
71
71
  auto_register: false
72
- ).create
72
+ ).create(force:)
73
73
  end
74
74
  end
75
75
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ module Generators
6
+ module App
7
+ # @api private
8
+ class Provider
9
+ def initialize(fs:, inflector:, out: $stdout)
10
+ @fs = fs
11
+ @inflector = inflector
12
+ @out = out
13
+ end
14
+
15
+ def call(key:, namespace:, base_path:, force: false, **_opts)
16
+ name = inflector.underscore(key)
17
+
18
+ registrar =
19
+ if base_path == "app"
20
+ "Hanami.app"
21
+ else
22
+ "#{inflector.camelize(namespace)}::Slice"
23
+ end
24
+
25
+ base_path = nil if base_path == "app" # Providers live in config/, not app/
26
+ path = fs.join(*[base_path, "config", "providers", "#{name}.rb"].compact)
27
+
28
+ fs.create(path, file_contents(registrar, name), force:)
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :fs, :inflector, :out
34
+
35
+ def file_contents(registrar, name)
36
+ <<~RUBY
37
+ # frozen_string_literal: true
38
+
39
+ #{registrar}.register_provider :#{name} do
40
+ # Define your provider here.
41
+ #
42
+ # See https://hanakai.org/learn/hanami/app/providers for details.
43
+
44
+ start do
45
+ # Set up and register the provider's components.
46
+ end
47
+ end
48
+ RUBY
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -19,7 +19,7 @@ module Hanami
19
19
 
20
20
  # @since 2.2.0
21
21
  # @api private
22
- def call(key:, namespace:, base_path:, gateway:)
22
+ def call(key:, namespace:, base_path:, gateway:, force: false)
23
23
  schema_name = inflector.underscore(key.split(KEY_SEPARATOR).last)
24
24
  body_content = ["schema :#{schema_name}, infer: true"]
25
25
 
@@ -34,7 +34,7 @@ module Hanami
34
34
  extra_namespace: "Relations",
35
35
  parent_class_name: "#{inflector.camelize(namespace)}::DB::Relation",
36
36
  body: body_content
37
- ).create
37
+ ).create(force:)
38
38
  end
39
39
 
40
40
  private
@@ -17,7 +17,7 @@ module Hanami
17
17
 
18
18
  # @since 2.2.0
19
19
  # @api private
20
- def call(key:, namespace:, base_path:)
20
+ def call(key:, namespace:, base_path:, force: false)
21
21
  RubyClassFile.new(
22
22
  fs: fs,
23
23
  inflector: inflector,
@@ -26,7 +26,7 @@ module Hanami
26
26
  base_path: base_path,
27
27
  extra_namespace: "Repos",
28
28
  parent_class_name: "#{inflector.camelize(namespace)}::DB::Repo"
29
- ).create
29
+ ).create(force:)
30
30
  end
31
31
 
32
32
  private