hanami-cli 2.1.1 → 2.2.0.beta2

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 (80) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +29 -13
  3. data/.rubocop.yml +2 -0
  4. data/CHANGELOG.md +28 -0
  5. data/Gemfile +6 -1
  6. data/README.md +13 -7
  7. data/docker-compose.yml +14 -0
  8. data/hanami-cli.gemspec +2 -2
  9. data/lib/hanami/cli/command.rb +2 -2
  10. data/lib/hanami/cli/commands/app/command.rb +2 -16
  11. data/lib/hanami/cli/commands/app/db/command.rb +148 -0
  12. data/lib/hanami/cli/commands/app/db/create.rb +21 -11
  13. data/lib/hanami/cli/commands/app/db/drop.rb +21 -10
  14. data/lib/hanami/cli/commands/app/db/migrate.rb +50 -12
  15. data/lib/hanami/cli/commands/app/db/prepare.rb +68 -0
  16. data/lib/hanami/cli/commands/app/db/seed.rb +22 -21
  17. data/lib/hanami/cli/commands/app/db/structure/dump.rb +32 -7
  18. data/lib/hanami/cli/commands/app/db/structure/load.rb +54 -0
  19. data/lib/hanami/cli/commands/app/db/utils/database.rb +100 -75
  20. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +59 -6
  21. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +42 -21
  22. data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +58 -10
  23. data/lib/hanami/cli/commands/app/db/version.rb +14 -8
  24. data/lib/hanami/cli/commands/app/generate/action.rb +4 -3
  25. data/lib/hanami/cli/commands/app/generate/command.rb +60 -0
  26. data/lib/hanami/cli/commands/app/generate/component.rb +49 -0
  27. data/lib/hanami/cli/commands/app/generate/migration.rb +47 -0
  28. data/lib/hanami/cli/commands/app/generate/operation.rb +26 -0
  29. data/lib/hanami/cli/commands/app/generate/part.rb +1 -1
  30. data/lib/hanami/cli/commands/app/generate/relation.rb +29 -0
  31. data/lib/hanami/cli/commands/app/generate/repo.rb +42 -0
  32. data/lib/hanami/cli/commands/app/generate/slice.rb +20 -3
  33. data/lib/hanami/cli/commands/app/generate/struct.rb +27 -0
  34. data/lib/hanami/cli/commands/app/install.rb +1 -1
  35. data/lib/hanami/cli/commands/app/middleware.rb +1 -1
  36. data/lib/hanami/cli/commands/app/server.rb +2 -2
  37. data/lib/hanami/cli/commands/app.rb +21 -2
  38. data/lib/hanami/cli/commands/gem/new.rb +78 -14
  39. data/lib/hanami/cli/errors.rb +28 -0
  40. data/lib/hanami/cli/files.rb +22 -0
  41. data/lib/hanami/cli/generators/app/action_context.rb +5 -13
  42. data/lib/hanami/cli/generators/app/component/component.erb +8 -0
  43. data/lib/hanami/cli/generators/app/component/slice_component.erb +8 -0
  44. data/lib/hanami/cli/generators/app/component.rb +61 -0
  45. data/lib/hanami/cli/generators/app/component_context.rb +82 -0
  46. data/lib/hanami/cli/generators/app/migration.rb +66 -0
  47. data/lib/hanami/cli/generators/app/operation.rb +49 -0
  48. data/lib/hanami/cli/generators/app/part_context.rb +5 -21
  49. data/lib/hanami/cli/generators/app/relation.rb +45 -0
  50. data/lib/hanami/cli/generators/app/repo.rb +41 -0
  51. data/lib/hanami/cli/generators/app/ruby_file_writer.rb +151 -0
  52. data/lib/hanami/cli/generators/app/slice/{entities.erb → operation.erb} +1 -3
  53. data/lib/hanami/cli/generators/app/slice/relation.erb +8 -0
  54. data/lib/hanami/cli/generators/app/slice/{slice.erb → repo.erb} +3 -1
  55. data/lib/hanami/cli/generators/app/slice/struct.erb +8 -0
  56. data/lib/hanami/cli/generators/app/slice.rb +14 -6
  57. data/lib/hanami/cli/generators/app/slice_context.rb +9 -2
  58. data/lib/hanami/cli/generators/app/struct.rb +40 -0
  59. data/lib/hanami/cli/generators/app/view_context.rb +4 -16
  60. data/lib/hanami/cli/generators/constants.rb +39 -0
  61. data/lib/hanami/cli/generators/context.rb +48 -0
  62. data/lib/hanami/cli/generators/gem/app/action.erb +3 -0
  63. data/lib/hanami/cli/generators/gem/app/env.erb +4 -0
  64. data/lib/hanami/cli/generators/gem/app/gemfile.erb +11 -0
  65. data/lib/hanami/cli/generators/gem/app/gitignore.erb +4 -1
  66. data/lib/hanami/cli/generators/gem/app/operation.erb +13 -0
  67. data/lib/hanami/cli/generators/gem/app/relation.erb +10 -0
  68. data/lib/hanami/cli/generators/gem/app/repo.erb +10 -0
  69. data/lib/hanami/cli/generators/gem/app/struct.erb +10 -0
  70. data/lib/hanami/cli/generators/gem/app.rb +19 -0
  71. data/lib/hanami/cli/ruby_file_generator.rb +123 -0
  72. data/lib/hanami/cli/version.rb +1 -1
  73. metadata +39 -17
  74. data/lib/hanami/cli/commands/app/db/create_migration.rb +0 -32
  75. data/lib/hanami/cli/commands/app/db/reset.rb +0 -28
  76. data/lib/hanami/cli/commands/app/db/rollback.rb +0 -81
  77. data/lib/hanami/cli/commands/app/db/sample_data.rb +0 -42
  78. data/lib/hanami/cli/commands/app/db/setup.rb +0 -26
  79. data/lib/hanami/cli/commands/app/db/utils/database_config.rb +0 -60
  80. data/lib/hanami/cli/generators/app/slice/repository.erb +0 -10
@@ -1,24 +1,30 @@
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"]
12
+ option :gateway, required: false, desc: "Use database for gateway"
15
13
 
16
14
  # @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"
15
+ def call(app: false, slice: nil, gateway: nil, **)
16
+ databases(app: app, slice: slice, gateway: gateway).each do |database|
17
+ unless database.migrations_dir?
18
+ relative_migrations_path = database.migrations_path.relative_path_from(database.slice.app.root)
19
+ out.puts "=> Cannot find version for database #{database.name}: no migrations directory at #{relative_migrations_path}/"
20
+ return
21
+ end
22
+
23
+ migration = database.applied_migrations.last
24
+ version = migration ? File.basename(migration, ".*") : "not available"
20
25
 
21
- out.puts "=> current schema version is #{version}"
26
+ out.puts "=> #{database.name} current schema version is #{version}"
27
+ end
22
28
  end
23
29
  end
24
30
  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,60 @@
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
+ if slice
42
+ generator.call(
43
+ key: name,
44
+ namespace: slice,
45
+ base_path: fs.join("slices", inflector.underscore(slice))
46
+ )
47
+ else
48
+ generator.call(
49
+ key: name,
50
+ namespace: app.namespace,
51
+ base_path: "app"
52
+ )
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ 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,47 @@
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
+ option :gateway, desc: "Generate migration for gateway"
13
+
14
+ example [
15
+ %(create_posts),
16
+ %(add_published_at_to_posts),
17
+ %(create_users --slice=admin),
18
+ %(create_comments --slice=admin --gateway=extra),
19
+ ]
20
+
21
+ def generator_class
22
+ Generators::App::Migration
23
+ end
24
+
25
+ def call(name:, slice: nil, gateway: nil)
26
+ if slice
27
+ generator.call(
28
+ key: name,
29
+ namespace: slice,
30
+ base_path: fs.join("slices", inflector.underscore(slice)),
31
+ gateway: gateway
32
+ )
33
+ else
34
+ generator.call(
35
+ key: name,
36
+ namespace: app.namespace,
37
+ base_path: "app",
38
+ gateway: gateway
39
+ )
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ 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,29 @@
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
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
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 Repo < Command
17
+ argument :name, required: true, desc: "Repo name"
18
+
19
+ example [
20
+ %(books (MyApp::Repos::BooksRepo)),
21
+ %(books/drafts_repo (MyApp::Repos::Books::DraftsRepo)),
22
+ %(books --slice=admin (Admin::Repos::BooksRepo)),
23
+ ]
24
+
25
+ # @since 2.2.0
26
+ # @api private
27
+ def generator_class
28
+ Generators::App::Repo
29
+ end
30
+
31
+ # @since 2.2.0
32
+ # @api private
33
+ def call(name:, **opts)
34
+ name = "#{inflector.singularize(name)}_repo" unless name.end_with?("_repo")
35
+ super
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -12,10 +12,23 @@ module Hanami
12
12
  module Generate
13
13
  # @since 2.0.0
14
14
  # @api private
15
- class Slice < Command
15
+ class Slice < App::Command
16
16
  argument :name, required: true, desc: "The slice name"
17
17
  option :url, required: false, type: :string, desc: "The slice URL prefix"
18
18
 
19
+ # @since 2.2.0
20
+ # @api private
21
+ SKIP_DB_DEFAULT = false
22
+ private_constant :SKIP_DB_DEFAULT
23
+
24
+ # @since 2.2.0
25
+ # @api private
26
+ option :skip_db,
27
+ type: :boolean,
28
+ required: false,
29
+ default: SKIP_DB_DEFAULT,
30
+ desc: "Skip database"
31
+
19
32
  example [
20
33
  "admin # Admin slice (/admin URL prefix)",
21
34
  "users --url=/u # Users slice (/u URL prefix)",
@@ -34,14 +47,18 @@ module Hanami
34
47
 
35
48
  # @since 2.0.0
36
49
  # @api private
37
- def call(name:, url: nil, **)
50
+ def call(
51
+ name:,
52
+ url: nil,
53
+ skip_db: SKIP_DB_DEFAULT
54
+ )
38
55
  require "hanami/setup"
39
56
 
40
57
  app = inflector.underscore(Hanami.app.namespace)
41
58
  name = inflector.underscore(Shellwords.shellescape(name))
42
59
  url = sanitize_url_prefix(name, url)
43
60
 
44
- generator.call(app, name, url)
61
+ generator.call(app, name, url, skip_db: skip_db)
45
62
  end
46
63
 
47
64
  private
@@ -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 Struct < Command
11
+ argument :name, required: true, desc: "Struct name"
12
+
13
+ example [
14
+ %(book (MyApp::Structs::Book)),
15
+ %(book/published_book (MyApp::Structs::Book::PublishedBook)),
16
+ %(book --slice=admin (Admin::Structs::Book)),
17
+ ]
18
+
19
+ def generator_class
20
+ Generators::App::Struct
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -31,7 +31,7 @@ module Hanami
31
31
 
32
32
  # @since 2.1.0
33
33
  # @api private
34
- option :head, type: :boolean, desc: "Install head deps", default: DEFAULT_HEAD
34
+ option :head, type: :flag, desc: "Install head deps", default: DEFAULT_HEAD
35
35
 
36
36
  # @since 2.0.0
37
37
  # @api private
@@ -34,7 +34,7 @@ module Hanami
34
34
  private_constant :DEFAULT_WITH_ARGUMENTS
35
35
 
36
36
  option :with_arguments, default: DEFAULT_WITH_ARGUMENTS, required: false,
37
- desc: "Include inspected arguments", type: :boolean
37
+ desc: "Include inspected arguments", type: :flag
38
38
 
39
39
  example [
40
40
  "middleware # Print app Rack middleware stack",
@@ -41,8 +41,8 @@ module Hanami
41
41
  option :port, default: Hanami::Port::DEFAULT, required: false,
42
42
  desc: "The port to run the server on (falls back to the rack handler)"
43
43
  option :config, default: DEFAULT_CONFIG_PATH, required: false, desc: "Rack configuration file"
44
- option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :boolean
45
- option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :boolean
44
+ option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :flag
45
+ option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :flag
46
46
 
47
47
  # @since 2.0.0
48
48
  # @api private
@@ -27,11 +27,30 @@ module Hanami
27
27
  end
28
28
  end
29
29
 
30
+ if Hanami.bundled?("hanami-db")
31
+ register "db" do |db|
32
+ db.register "create", DB::Create
33
+ db.register "drop", DB::Drop
34
+ db.register "migrate", DB::Migrate
35
+ db.register "structure dump", DB::Structure::Dump
36
+ db.register "structure load", DB::Structure::Load
37
+ db.register "seed", DB::Seed
38
+ db.register "prepare", DB::Prepare
39
+ db.register "version", DB::Version
40
+ end
41
+ end
42
+
30
43
  register "generate", aliases: ["g"] do |prefix|
31
- prefix.register "slice", Generate::Slice
32
44
  prefix.register "action", Generate::Action
33
- prefix.register "view", Generate::View
45
+ prefix.register "component", Generate::Component
46
+ prefix.register "migration", Generate::Migration
47
+ prefix.register "operation", Generate::Operation
34
48
  prefix.register "part", Generate::Part
49
+ prefix.register "relation", Generate::Relation
50
+ prefix.register "repo", Generate::Repo
51
+ prefix.register "slice", Generate::Slice
52
+ prefix.register "struct", Generate::Struct
53
+ prefix.register "view", Generate::View
35
54
  end
36
55
  end
37
56
  end
@@ -25,6 +25,27 @@ module Hanami
25
25
  SKIP_ASSETS_DEFAULT = false
26
26
  private_constant :SKIP_ASSETS_DEFAULT
27
27
 
28
+ # @since 2.2.0
29
+ # @api private
30
+ SKIP_DB_DEFAULT = false
31
+ private_constant :SKIP_DB_DEFAULT
32
+
33
+ # @since 2.2.0
34
+ # @api private
35
+ DATABASE_SQLITE = "sqlite"
36
+
37
+ # @since 2.2.0
38
+ # @api private
39
+ DATABASE_POSTGRES = "postgres"
40
+
41
+ # @since 2.2.0
42
+ # @api private
43
+ DATABASE_MYSQL = "mysql"
44
+
45
+ # @since 2.2.0
46
+ # @api private
47
+ SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze
48
+
28
49
  desc "Generate a new Hanami app"
29
50
 
30
51
  # @since 2.0.0
@@ -33,28 +54,42 @@ module Hanami
33
54
 
34
55
  # @since 2.0.0
35
56
  # @api private
36
- option :skip_install, type: :boolean, required: false,
57
+ option :skip_install, type: :flag, required: false,
37
58
  default: SKIP_INSTALL_DEFAULT,
38
59
  desc: "Skip app installation (Bundler, third-party Hanami plugins)"
39
60
 
40
61
  # @since 2.1.0
41
62
  # @api private
42
- option :head, type: :boolean, required: false,
63
+ option :head, type: :flag, required: false,
43
64
  default: HEAD_DEFAULT,
44
65
  desc: "Use Hanami HEAD version (from GitHub `main` branches)"
45
66
 
46
67
  # @since 2.1.0
47
68
  # @api private
48
- option :skip_assets, type: :boolean, required: false,
69
+ option :skip_assets, type: :flag, required: false,
49
70
  default: SKIP_ASSETS_DEFAULT,
50
- desc: "Skip assets"
71
+ desc: "Skip including hanami-assets"
72
+
73
+ # @since 2.2.0
74
+ # @api private
75
+ option :skip_db, type: :flag, required: false,
76
+ default: SKIP_DB_DEFAULT,
77
+ desc: "Skip including hanami-db"
78
+
79
+ # @since 2.2.0
80
+ # @api private
81
+ option :database, type: :string, required: false,
82
+ default: DATABASE_SQLITE,
83
+ desc: "Database adapter (supported: sqlite, mysql, postgres)"
51
84
 
52
85
  # rubocop:disable Layout/LineLength
53
86
  example [
54
- "bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
55
- "bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
56
- "bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
57
- "bookshelf --skip-assets # Generate a new Hanami app without assets"
87
+ "bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
88
+ "bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
89
+ "bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
90
+ "bookshelf --skip-assets # Generate a new Hanami app without hanmai-assets",
91
+ "bookshelf --skip-db # Generate a new Hanami app without hanami-db",
92
+ "bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
58
93
  ]
59
94
  # rubocop:enable Layout/LineLength
60
95
 
@@ -75,20 +110,36 @@ module Hanami
75
110
  @system_call = system_call
76
111
  end
77
112
 
78
- # rubocop:enable Metrics/ParameterLists
79
-
80
- # rubocop:disable Metrics/AbcSize
113
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity
81
114
 
82
115
  # @since 2.0.0
83
116
  # @api private
84
- def call(app:, head: HEAD_DEFAULT, skip_install: SKIP_INSTALL_DEFAULT, skip_assets: SKIP_ASSETS_DEFAULT, **)
117
+ def call(
118
+ app:,
119
+ head: HEAD_DEFAULT,
120
+ skip_install: SKIP_INSTALL_DEFAULT,
121
+ skip_assets: SKIP_ASSETS_DEFAULT,
122
+ skip_db: SKIP_DB_DEFAULT,
123
+ database: nil
124
+ )
125
+ # rubocop:enable Metrics/ParameterLists
85
126
  app = inflector.underscore(app)
86
127
 
87
128
  raise PathAlreadyExistsError.new(app) if fs.exist?(app)
129
+ raise ConflictingOptionsError.new("--skip-db", "--database") if skip_db && database
130
+
131
+ normalized_database ||= normalize_database(database)
88
132
 
89
133
  fs.mkdir(app)
90
134
  fs.chdir(app) do
91
- context = Generators::Context.new(inflector, app, head: head, skip_assets: skip_assets)
135
+ context = Generators::Context.new(
136
+ inflector,
137
+ app,
138
+ head: head,
139
+ skip_assets: skip_assets,
140
+ skip_db: skip_db,
141
+ database: normalized_database
142
+ )
92
143
  generator.call(app, context: context) do
93
144
  if skip_install
94
145
  out.puts "Skipping installation, please enter `#{app}' directory and run `bundle exec hanami install'"
@@ -112,7 +163,7 @@ module Hanami
112
163
  end
113
164
  end
114
165
  end
115
- # rubocop:enable Metrics/AbcSize
166
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity
116
167
 
117
168
  private
118
169
 
@@ -120,6 +171,19 @@ module Hanami
120
171
  attr_reader :generator
121
172
  attr_reader :system_call
122
173
 
174
+ def normalize_database(database)
175
+ case database
176
+ when nil, "sqlite", "sqlite3"
177
+ DATABASE_SQLITE
178
+ when "mysql", "mysql2"
179
+ DATABASE_MYSQL
180
+ when "postgres", "postgresql", "pg"
181
+ DATABASE_POSTGRES
182
+ else
183
+ raise DatabaseNotSupportedError.new(database, SUPPORTED_DATABASES)
184
+ end
185
+ end
186
+
123
187
  def run_install_command!(head:)
124
188
  head_flag = head ? " --head" : ""
125
189
  bundler.exec("hanami install#{head_flag}").tap do |result|
@@ -91,5 +91,33 @@ module Hanami
91
91
  super("`#{scheme}' is not a supported db scheme")
92
92
  end
93
93
  end
94
+
95
+ # @since 2.2.0
96
+ # @api public
97
+ class DatabaseNotSupportedError < Error
98
+ def initialize(invalid_database, supported_databases)
99
+ super("`#{invalid_database}' is not a supported database. Supported databases are: #{supported_databases.join(', ')}")
100
+ end
101
+ end
102
+
103
+ # @since 2.2.0
104
+ # @api public
105
+ class ConflictingOptionsError < Error
106
+ def initialize(option1, option2)
107
+ super("`#{option1}' and `#{option2}' cannot be used together")
108
+ end
109
+ end
110
+
111
+ # @since 2.2.0
112
+ # @api public
113
+ class InvalidMigrationNameError < Error
114
+ def initialize(name)
115
+ super(<<~TEXT)
116
+ Invalid migration name: #{name}
117
+
118
+ Name must contain only letters, numbers, and underscores.
119
+ TEXT
120
+ end
121
+ end
94
122
  end
95
123
  end