hanami-cli 2.1.1 → 2.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +18 -13
  3. data/CHANGELOG.md +15 -0
  4. data/Gemfile +4 -2
  5. data/README.md +11 -7
  6. data/docker-compose.yml +9 -0
  7. data/hanami-cli.gemspec +2 -2
  8. data/lib/hanami/cli/command.rb +1 -1
  9. data/lib/hanami/cli/commands/app/command.rb +2 -16
  10. data/lib/hanami/cli/commands/app/db/command.rb +116 -0
  11. data/lib/hanami/cli/commands/app/db/create.rb +19 -11
  12. data/lib/hanami/cli/commands/app/db/drop.rb +19 -10
  13. data/lib/hanami/cli/commands/app/db/migrate.rb +19 -13
  14. data/lib/hanami/cli/commands/app/db/prepare.rb +42 -0
  15. data/lib/hanami/cli/commands/app/db/seed.rb +11 -22
  16. data/lib/hanami/cli/commands/app/db/structure/dump.rb +30 -7
  17. data/lib/hanami/cli/commands/app/db/structure/load.rb +52 -0
  18. data/lib/hanami/cli/commands/app/db/utils/database.rb +68 -73
  19. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +2 -2
  20. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +38 -19
  21. data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +58 -10
  22. data/lib/hanami/cli/commands/app/db/version.rb +12 -9
  23. data/lib/hanami/cli/commands/app/generate/action.rb +4 -3
  24. data/lib/hanami/cli/commands/app/generate/command.rb +49 -0
  25. data/lib/hanami/cli/commands/app/generate/component.rb +49 -0
  26. data/lib/hanami/cli/commands/app/generate/migration.rb +27 -0
  27. data/lib/hanami/cli/commands/app/generate/operation.rb +26 -0
  28. data/lib/hanami/cli/commands/app/generate/part.rb +1 -1
  29. data/lib/hanami/cli/commands/app/generate/relation.rb +35 -0
  30. data/lib/hanami/cli/commands/app/generate/repo.rb +46 -0
  31. data/lib/hanami/cli/commands/app/generate/slice.rb +20 -3
  32. data/lib/hanami/cli/commands/app/generate/struct.rb +27 -0
  33. data/lib/hanami/cli/commands/app/install.rb +1 -1
  34. data/lib/hanami/cli/commands/app/middleware.rb +1 -1
  35. data/lib/hanami/cli/commands/app/server.rb +2 -2
  36. data/lib/hanami/cli/commands/app.rb +21 -2
  37. data/lib/hanami/cli/commands/gem/new.rb +78 -14
  38. data/lib/hanami/cli/errors.rb +28 -0
  39. data/lib/hanami/cli/generators/app/action_context.rb +5 -13
  40. data/lib/hanami/cli/generators/app/component/component.erb +8 -0
  41. data/lib/hanami/cli/generators/app/component/slice_component.erb +8 -0
  42. data/lib/hanami/cli/generators/app/component.rb +61 -0
  43. data/lib/hanami/cli/generators/app/component_context.rb +82 -0
  44. data/lib/hanami/cli/generators/app/migration.rb +69 -0
  45. data/lib/hanami/cli/generators/app/operation.rb +48 -0
  46. data/lib/hanami/cli/generators/app/part_context.rb +5 -21
  47. data/lib/hanami/cli/generators/app/relation.rb +44 -0
  48. data/lib/hanami/cli/generators/app/repo.rb +40 -0
  49. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +149 -0
  50. data/lib/hanami/cli/generators/app/slice/operation.erb +7 -0
  51. data/lib/hanami/cli/generators/app/slice/relation.erb +8 -0
  52. data/lib/hanami/cli/generators/app/slice/{slice.erb → repo.erb} +3 -1
  53. data/lib/hanami/cli/generators/app/slice/struct.erb +8 -0
  54. data/lib/hanami/cli/generators/app/slice.rb +14 -6
  55. data/lib/hanami/cli/generators/app/slice_context.rb +9 -2
  56. data/lib/hanami/cli/generators/app/struct.rb +39 -0
  57. data/lib/hanami/cli/generators/app/view_context.rb +4 -16
  58. data/lib/hanami/cli/generators/constants.rb +39 -0
  59. data/lib/hanami/cli/generators/context.rb +48 -0
  60. data/lib/hanami/cli/generators/gem/app/action.erb +3 -0
  61. data/lib/hanami/cli/generators/gem/app/env.erb +4 -0
  62. data/lib/hanami/cli/generators/gem/app/gemfile.erb +11 -0
  63. data/lib/hanami/cli/generators/gem/app/gitignore.erb +1 -1
  64. data/lib/hanami/cli/generators/gem/app/operation.erb +13 -0
  65. data/lib/hanami/cli/generators/gem/app/relation.erb +10 -0
  66. data/lib/hanami/cli/generators/gem/app/repo.erb +10 -0
  67. data/lib/hanami/cli/generators/gem/app/struct.erb +10 -0
  68. data/lib/hanami/cli/generators/gem/app.rb +19 -0
  69. data/lib/hanami/cli/ruby_file_generator.rb +123 -0
  70. data/lib/hanami/cli/version.rb +1 -1
  71. metadata +39 -16
  72. data/lib/hanami/cli/commands/app/db/create_migration.rb +0 -32
  73. data/lib/hanami/cli/commands/app/db/reset.rb +0 -28
  74. data/lib/hanami/cli/commands/app/db/rollback.rb +0 -81
  75. data/lib/hanami/cli/commands/app/db/sample_data.rb +0 -42
  76. data/lib/hanami/cli/commands/app/db/setup.rb +0 -26
  77. data/lib/hanami/cli/commands/app/db/utils/database_config.rb +0 -60
  78. data/lib/hanami/cli/generators/app/slice/repository.erb +0 -10
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "database_config"
3
+ require "uri"
4
4
 
5
5
  module Hanami
6
6
  module CLI
@@ -9,15 +9,14 @@ module Hanami
9
9
  module DB
10
10
  module Utils
11
11
  # @api private
12
+ # @since 2.2.0
12
13
  class Database
13
- # @api private
14
- attr_reader :app
14
+ MIGRATIONS_DIR = "config/db/migrate"
15
+ private_constant :MIGRATIONS_DIR
15
16
 
16
- # @api private
17
- attr_reader :config
18
-
19
- # @api private
20
- SCHEME_MAP = {
17
+ DATABASE_CLASS_RESOLVER = Hash.new { |_, key|
18
+ raise "#{key} is not a supported db scheme"
19
+ }.update(
21
20
  "sqlite" => -> {
22
21
  require_relative("sqlite")
23
22
  Sqlite
@@ -34,84 +33,66 @@ module Hanami
34
33
  require_relative("mysql")
35
34
  Mysql
36
35
  }
37
- }.freeze
38
-
39
- # @api private
40
- def self.[](app)
41
- database_url =
42
- if app.key?(:settings) && app[:settings].respond_to?(:database_url)
43
- app[:settings].database_url
44
- else
45
- ENV.fetch("DATABASE_URL")
46
- end
36
+ ).freeze
47
37
 
48
- config = DatabaseConfig.new(database_url)
38
+ def self.[](slice, system_call:)
39
+ provider = slice.container.providers[:db]
40
+ raise "this is not a db slice" unless provider
49
41
 
50
- resolver = SCHEME_MAP.fetch(config.db_type) do
51
- raise "#{config.db_type} is not a supported db scheme"
52
- end
42
+ database_scheme = provider.source.database_url.then { URI(_1).scheme }
43
+ database_class = DATABASE_CLASS_RESOLVER[database_scheme].call
44
+ database_class.new(slice: slice, system_call: system_call)
45
+ end
46
+
47
+ attr_reader :slice
53
48
 
54
- klass = resolver.()
49
+ attr_reader :system_call
55
50
 
56
- klass.new(app: app, config: config)
51
+ def initialize(slice:, system_call:)
52
+ @slice = slice
53
+ @system_call = system_call
57
54
  end
58
55
 
59
- # @api private
60
- def initialize(app:, config:)
61
- @app = app
62
- @config = config
56
+ def name
57
+ database_uri.path.sub(%r{^/}, "")
63
58
  end
64
59
 
65
- # @api private
66
- def create_command
67
- raise Hanami::CLI::NotImplementedError
60
+ def database_url
61
+ slice.container.providers[:db].source.database_url
68
62
  end
69
63
 
70
- # @api private
71
- def drop_command
72
- raise Hanami::CLI::NotImplementedError
64
+ def database_uri
65
+ @database_uri ||= URI(database_url)
73
66
  end
74
67
 
75
- # @api private
76
- def dump_command
77
- raise Hanami::CLI::NotImplementedError
68
+ def gateway
69
+ slice["db.config"].gateways[:default]
78
70
  end
79
71
 
80
- # @api private
81
- def load_command
82
- raise Hanami::CLI::NotImplementedError
72
+ def connection
73
+ gateway.connection
83
74
  end
84
75
 
85
- # @api private
86
- def root_path
87
- app.root
76
+ def exec_create_command
77
+ raise Hanami::CLI::NotImplementedError
88
78
  end
89
79
 
90
- # @api private
91
- def rom_config
92
- @rom_config ||=
93
- begin
94
- app.prepare(:persistence)
95
- app.container["persistence.config"]
96
- end
80
+ def exec_drop_command
81
+ raise Hanami::CLI::NotImplementedError
97
82
  end
98
83
 
99
- # @api private
100
- def name
101
- config.db_name
84
+ def exists?
85
+ raise Hanami::CLI::NotImplementedError
102
86
  end
103
87
 
104
- # @api private
105
- def gateway
106
- rom_config.gateways[:default]
88
+ def exec_dump_command
89
+ raise Hanami::CLI::NotImplementedError
107
90
  end
108
91
 
109
- # @api private
110
- def connection
111
- gateway.connection
92
+ def exec_load_command
93
+ raise Hanami::CLI::NotImplementedError
112
94
  end
113
95
 
114
- # @api private
115
96
  def run_migrations(**options)
116
97
  require "rom/sql"
117
98
  ROM::SQL.with_gateway(gateway) do
@@ -119,24 +100,19 @@ module Hanami
119
100
  end
120
101
  end
121
102
 
122
- # @api private
123
103
  def migrator
124
- @migrator ||=
125
- begin
126
- require "rom/sql"
127
- ROM::SQL::Migration::Migrator.new(connection, path: File.join(root_path, "db/migrate"))
128
- end
129
- end
104
+ @migrator ||= begin
105
+ slice.prepare :db
130
106
 
131
- # @api private
132
- def applied_migrations
133
- sequel_migrator.applied_migrations
107
+ require "rom/sql"
108
+ ROM::SQL::Migration::Migrator.new(connection, path: migrations_path)
109
+ end
134
110
  end
135
111
 
136
- private
137
-
138
112
  def sequel_migrator
139
113
  @sequel_migrator ||= begin
114
+ slice.prepare :db
115
+
140
116
  require "sequel"
141
117
  Sequel.extension :migration
142
118
 
@@ -147,8 +123,27 @@ module Hanami
147
123
  end
148
124
  end
149
125
 
126
+ def applied_migrations
127
+ sequel_migrator.applied_migrations
128
+ end
129
+
150
130
  def migrations_path
151
- File.join(root_path, "db/migrate")
131
+ slice.root.join(MIGRATIONS_DIR)
132
+ end
133
+
134
+ def migrations_dir?
135
+ migrations_path.directory?
136
+ end
137
+
138
+ def structure_file
139
+ slice.root.join("config/db/structure.sql")
140
+ end
141
+
142
+ def schema_migrations_sql_dump
143
+ sql = +"INSERT INTO schema_migrations (filename) VALUES\n"
144
+ sql << applied_migrations.map { |v| "('#{v}')" }.join(",\n")
145
+ sql << ";"
146
+ sql
152
147
  end
153
148
  end
154
149
  end
@@ -16,12 +16,12 @@ module Hanami
16
16
  end
17
17
 
18
18
  # @api private
19
- def dump_command
19
+ def exec_dump_command
20
20
  raise Hanami::CLI::NotImplementedError
21
21
  end
22
22
 
23
23
  # @api private
24
- def load_command
24
+ def exec_load_command
25
25
  raise Hanami::CLI::NotImplementedError
26
26
  end
27
27
  end
@@ -11,49 +11,68 @@ module Hanami
11
11
  module DB
12
12
  module Utils
13
13
  # @api private
14
+ # @since 2.2.0
14
15
  class Postgres < Database
15
16
  # @api private
16
- def create_command
17
- existing_stdout, status = Open3.capture2(cli_env_vars, "psql -t -c '\\l #{escaped_name}'")
17
+ # @since 2.2.0
18
+ def exec_create_command
19
+ return true if exists?
18
20
 
19
- return true if status.success? && existing_stdout.include?(escaped_name)
20
-
21
- system(cli_env_vars, "createdb #{escaped_name}")
21
+ system_call.call("createdb #{escaped_name}", env: cli_env_vars)
22
22
  end
23
23
 
24
24
  # @api private
25
- def drop_command
26
- system(cli_env_vars, "dropdb #{escaped_name}")
25
+ # @since 2.2.0
26
+ def exec_drop_command
27
+ return true unless exists?
28
+
29
+ system_call.call("dropdb #{escaped_name}", env: cli_env_vars)
27
30
  end
28
31
 
29
32
  # @api private
30
- def dump_command
31
- system(cli_env_vars, "pg_dump --schema-only --no-owner #{escaped_name} > #{dump_file}")
33
+ # @since 2.2.0
34
+ def exists?
35
+ result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
36
+ result.successful? && result.out.include?("#{name}|") # start_with?
32
37
  end
33
38
 
34
39
  # @api private
35
- def load_command
36
- raise "Not Implemented Yet"
40
+ # @since 2.2.0
41
+ def exec_dump_command
42
+ system_call.call(
43
+ "pg_dump --schema-only --no-privileges --no-owner --file #{structure_file} #{escaped_name}",
44
+ env: cli_env_vars
45
+ )
37
46
  end
38
47
 
39
48
  # @api private
49
+ # @since 2.2.0
50
+ def exec_load_command
51
+ system_call.call(
52
+ "psql --set ON_ERROR_STOP=1 --quiet --no-psqlrc --output #{File::NULL} --file #{structure_file} #{escaped_name}",
53
+ env: cli_env_vars
54
+ )
55
+ end
56
+
40
57
  def escaped_name
41
58
  Shellwords.escape(name)
42
59
  end
43
60
 
44
- # @api private
45
61
  def cli_env_vars
46
62
  @cli_env_vars ||= {}.tap do |vars|
47
- vars["PGHOST"] = config.host.to_s
48
- vars["PGPORT"] = config.port.to_s if config.port
49
- vars["PGUSER"] = config.user.to_s if config.user
50
- vars["PGPASSWORD"] = config.pass.to_s if config.pass
63
+ vars["PGHOST"] = database_uri.host.to_s if database_uri.host
64
+ vars["PGPORT"] = database_uri.port.to_s if database_uri.port
65
+ vars["PGUSER"] = database_uri.user.to_s if database_uri.user
66
+ vars["PGPASSWORD"] = database_uri.password.to_s if database_uri.password
51
67
  end
52
68
  end
53
69
 
54
- # @api private
55
- def dump_file
56
- "#{root_path}/db/structure.sql"
70
+ def schema_migrations_sql_dump
71
+ search_path = slice["db.gateway"].connection
72
+ .fetch("SHOW search_path").to_a.first
73
+ .fetch(:search_path)
74
+
75
+ +"SET search_path TO #{search_path};\n\n" << super
57
76
  end
58
77
  end
59
78
  end
@@ -9,32 +9,80 @@ module Hanami
9
9
  module DB
10
10
  module Utils
11
11
  # @api private
12
+ # @since 2.2.0
12
13
  class Sqlite < Database
13
14
  # @api private
14
- def create_command
15
- rom_config
16
- true
15
+ # @since 2.2.0
16
+ Failure = Struct.new(:err) do
17
+ def successful?
18
+ false
19
+ end
20
+
21
+ def exit_code
22
+ 1
23
+ end
24
+ end
25
+
26
+ # @api private
27
+ # @since 2.2.0
28
+ def exec_create_command
29
+ return true if exists?
30
+
31
+ FileUtils.mkdir_p(File.dirname(file_path))
32
+
33
+ system_call.call(%(sqlite3 #{file_path} "VACUUM;"))
17
34
  end
18
35
 
19
36
  # @api private
20
- def drop_command
21
- file_path.unlink
37
+ # @since 2.2.0
38
+ def exec_drop_command
39
+ begin
40
+ File.unlink(file_path) if exists?
41
+ rescue => e
42
+ # Mimic a system_call result
43
+ return Failure.new(e.message)
44
+ end
45
+
22
46
  true
23
47
  end
24
48
 
25
49
  # @api private
26
- def dump_command
27
- raise Hanami::CLI::NotImplementedError
50
+ # @since 2.2.0
51
+ def exists?
52
+ File.exist?(file_path)
53
+ end
54
+
55
+ # @api private
56
+ # @since 2.2.0
57
+ def exec_dump_command
58
+ system_call.call(%(sqlite3 #{file_path} ".schema --indent --nosys" > #{structure_file}))
28
59
  end
29
60
 
30
61
  # @api private
31
- def load_command
32
- raise Hanami::CLI::NotImplementedError
62
+ # @since 2.2.0
63
+ def exec_load_command
64
+ system_call.call("sqlite3 #{file_path} < #{structure_file}")
33
65
  end
34
66
 
35
67
  # @api private
68
+ # @since 2.2.0
69
+ def name
70
+ # Sequel expects sqlite:// URIs to operate the same as file:// URIs: 2 slashes for
71
+ # a relative path, 3 for an absolute path. In the case of 2 slashes, the first part
72
+ # of the path is considered by Ruby's `URI` as the `#host`.
73
+ @name ||= "#{database_uri.host}#{database_uri.path}"
74
+ end
75
+
76
+ private
77
+
36
78
  def file_path
37
- @file_path ||= Pathname("#{root_path}#{config.uri.path}").realpath
79
+ @file_path ||= begin
80
+ if File.absolute_path?(name)
81
+ name
82
+ else
83
+ slice.app.root.join(name).to_s
84
+ end
85
+ end
38
86
  end
39
87
  end
40
88
  end
@@ -1,24 +1,27 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "../../app/command"
4
-
5
3
  module Hanami
6
4
  module CLI
7
5
  module Commands
8
6
  module App
9
7
  module DB
10
8
  # @api private
11
- class Version < App::Command
9
+ class Version < DB::Command
12
10
  desc "Print schema version"
13
11
 
14
- option :target, desc: "Target migration number", aliases: ["-t"]
15
-
16
12
  # @api private
17
- def call(target: nil, **) # rubocop:disable Lint/UnusedMethodArgument
18
- migration = database.applied_migrations.last
19
- version = migration ? File.basename(migration, ".*") : "not available"
13
+ def call(app: false, slice: nil, **)
14
+ databases(app: app, slice: slice).each do |database|
15
+ unless database.migrations_dir?
16
+ out.puts "=> Cannot find version for slice #{database.slice.slice_name.to_s.inspect}: missing config/db/migrate/ dir"
17
+ return
18
+ end
19
+
20
+ migration = database.applied_migrations.last
21
+ version = migration ? File.basename(migration, ".*") : "not available"
20
22
 
21
- out.puts "=> current schema version is #{version}"
23
+ out.puts "=> #{database.name} current schema version is #{version}"
24
+ end
22
25
  end
23
26
  end
24
27
  end
@@ -32,13 +32,13 @@ module Hanami
32
32
  option \
33
33
  :skip_view,
34
34
  required: false,
35
- type: :boolean,
35
+ type: :flag,
36
36
  default: DEFAULT_SKIP_VIEW,
37
37
  desc: "Skip view and template generation"
38
38
  option \
39
39
  :skip_tests,
40
40
  required: false,
41
- type: :boolean,
41
+ type: :flag,
42
42
  default: DEFAULT_SKIP_TESTS,
43
43
  desc: "Skip test generation"
44
44
  option :slice, required: false, desc: "Slice name"
@@ -85,7 +85,8 @@ module Hanami
85
85
  skip_view: DEFAULT_SKIP_VIEW,
86
86
  skip_tests: DEFAULT_SKIP_TESTS, # rubocop:disable Lint/UnusedMethodArgument
87
87
  slice: nil,
88
- context: nil, **
88
+ context: nil,
89
+ **
89
90
  )
90
91
  slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
91
92
  name = naming.action_name(name)
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "dry/files"
5
+ require "shellwords"
6
+ require_relative "../../../naming"
7
+ require_relative "../../../errors"
8
+
9
+ module Hanami
10
+ module CLI
11
+ module Commands
12
+ module App
13
+ module Generate
14
+ # @since 2.2.0
15
+ # @api private
16
+ class Command < App::Command
17
+ option :slice, required: false, desc: "Slice name"
18
+
19
+ attr_reader :generator
20
+ private :generator
21
+
22
+ # @since 2.2.0
23
+ # @api private
24
+ def initialize(
25
+ fs:,
26
+ inflector:,
27
+ **opts
28
+ )
29
+ super
30
+ @generator = generator_class.new(fs: fs, inflector: inflector, out: out)
31
+ end
32
+
33
+ def generator_class
34
+ # Must be implemented by subclasses, with class that takes:
35
+ # fs:, inflector:, out:
36
+ end
37
+
38
+ # @since 2.2.0
39
+ # @api private
40
+ def call(name:, slice: nil, **)
41
+ normalized_slice = inflector.underscore(slice) if slice
42
+ generator.call(app.namespace, name, normalized_slice)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "dry/files"
5
+ require "shellwords"
6
+ module Hanami
7
+ module CLI
8
+ module Commands
9
+ module App
10
+ module Generate
11
+ # @api private
12
+ # @since 2.2.0
13
+ class Component < App::Command
14
+ argument :name, required: true, desc: "Component name"
15
+ option :slice, required: false, desc: "Slice name"
16
+
17
+ example [
18
+ %(operations.create_user (MyApp::Operations::CreateUser)),
19
+ %(operations.user.create (MyApp::Operations::Create::User)),
20
+ %(operations.create_user --slice=admin (Admin::Operations::CreateUser)),
21
+ %(Operations::CreateUser (MyApp::Operations::CreateUser)),
22
+ ]
23
+ attr_reader :generator
24
+ private :generator
25
+
26
+ # @api private
27
+ # @since 2.2.0
28
+ def initialize(
29
+ fs:, inflector:,
30
+ generator: Generators::App::Component.new(fs: fs, inflector: inflector),
31
+ **opts
32
+ )
33
+ @generator = generator
34
+ super(fs: fs, inflector: inflector, **opts)
35
+ end
36
+
37
+ # @api private
38
+ # @since 2.2.0
39
+ def call(name:, slice: nil, **)
40
+ slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
41
+
42
+ generator.call(app.namespace, name, slice)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ module Commands
6
+ module App
7
+ module Generate
8
+ # @since 2.2.0
9
+ # @api private
10
+ class Migration < Command
11
+ argument :name, required: true, desc: "Migration name"
12
+
13
+ example [
14
+ %(create_posts),
15
+ %(add_published_at_to_posts),
16
+ %(create_users --slice=admin),
17
+ ]
18
+
19
+ def generator_class
20
+ Generators::App::Migration
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ module Commands
6
+ module App
7
+ module Generate
8
+ # @since 2.2.0
9
+ # @api private
10
+ class Operation < Command
11
+ argument :name, required: true, desc: "Operation name"
12
+
13
+ example [
14
+ %(books.add (MyApp::Books::Add)),
15
+ %(books.add --slice=admin (Admin::Books::Add)),
16
+ ]
17
+
18
+ def generator_class
19
+ Generators::App::Operation
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -20,7 +20,7 @@ module Hanami
20
20
  option \
21
21
  :skip_tests,
22
22
  required: false,
23
- type: :boolean,
23
+ type: :flag,
24
24
  default: DEFAULT_SKIP_TESTS,
25
25
  desc: "Skip test generation"
26
26
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ module Commands
6
+ module App
7
+ module Generate
8
+ # @since 2.2.0
9
+ # @api private
10
+ class Relation < Command
11
+ argument :name, required: true, desc: "Relation name"
12
+
13
+ example [
14
+ %(books (MyApp::Relation::Book)),
15
+ %(books/drafts (MyApp::Relations::Books::Drafts)),
16
+ %(books --slice=admin (Admin::Relations::Books)),
17
+ ]
18
+
19
+ # @since 2.2.0
20
+ # @api private
21
+ def generator_class
22
+ Generators::App::Relation
23
+ end
24
+
25
+ # @since 2.2.0
26
+ # @api private
27
+ def call(name:, slice: nil, **opts)
28
+ super(name: name, slice: slice, **opts)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end