hanami-cli 2.0.0 → 2.1.0

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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -0
  3. data/{.action_hero.yml → .repobot.yml} +1 -3
  4. data/.rubocop.yml +6 -0
  5. data/CHANGELOG.md +115 -0
  6. data/Gemfile +7 -2
  7. data/LICENSE.md +1 -1
  8. data/README.md +1 -1
  9. data/hanami-cli.gemspec +1 -1
  10. data/lib/hanami/cli/bundler.rb +26 -9
  11. data/lib/hanami/cli/command.rb +28 -6
  12. data/lib/hanami/cli/commands/app/assets/command.rb +155 -0
  13. data/lib/hanami/cli/commands/app/assets/compile.rb +35 -0
  14. data/lib/hanami/cli/commands/app/assets/watch.rb +29 -0
  15. data/lib/hanami/cli/commands/app/assets.rb +16 -0
  16. data/lib/hanami/cli/commands/app/command.rb +5 -3
  17. data/lib/hanami/cli/commands/app/db/rollback.rb +30 -6
  18. data/lib/hanami/cli/commands/app/db/utils/database.rb +5 -1
  19. data/lib/hanami/cli/commands/app/dev.rb +50 -0
  20. data/lib/hanami/cli/commands/app/generate/action.rb +38 -9
  21. data/lib/hanami/cli/commands/app/generate/part.rb +57 -0
  22. data/lib/hanami/cli/commands/app/generate/slice.rb +6 -3
  23. data/lib/hanami/cli/commands/app/generate/view.rb +56 -0
  24. data/lib/hanami/cli/commands/app/generate.rb +2 -2
  25. data/lib/hanami/cli/commands/app/install.rb +16 -1
  26. data/lib/hanami/cli/commands/app/server.rb +6 -10
  27. data/lib/hanami/cli/commands/app.rb +10 -0
  28. data/lib/hanami/cli/commands/gem/new.rb +70 -12
  29. data/lib/hanami/cli/errors.rb +30 -0
  30. data/lib/hanami/cli/files.rb +8 -2
  31. data/lib/hanami/cli/generators/app/action/action.erb +5 -1
  32. data/lib/hanami/cli/generators/app/action/slice_action.erb +5 -1
  33. data/lib/hanami/cli/generators/app/action/slice_template.html.erb +1 -0
  34. data/lib/hanami/cli/generators/app/action/slice_view.erb +10 -0
  35. data/lib/hanami/cli/generators/app/action/template.html.erb +1 -2
  36. data/lib/hanami/cli/generators/app/action/view.erb +2 -5
  37. data/lib/hanami/cli/generators/app/action.rb +74 -22
  38. data/lib/hanami/cli/generators/app/action_context.rb +1 -1
  39. data/lib/hanami/cli/generators/app/part/app_base_part.erb +9 -0
  40. data/lib/hanami/cli/generators/app/part/app_part.erb +13 -0
  41. data/lib/hanami/cli/generators/app/part/slice_base_part.erb +9 -0
  42. data/lib/hanami/cli/generators/app/part/slice_part.erb +13 -0
  43. data/lib/hanami/cli/generators/app/part.rb +101 -0
  44. data/lib/hanami/cli/generators/app/part_context.rb +98 -0
  45. data/lib/hanami/cli/generators/app/slice/app_css.erb +5 -0
  46. data/lib/hanami/cli/generators/app/slice/app_js.erb +1 -0
  47. data/lib/hanami/cli/generators/app/slice/app_layout.erb +18 -0
  48. data/lib/hanami/cli/generators/app/slice/favicon.ico +0 -0
  49. data/lib/hanami/cli/generators/app/slice/helpers.erb +10 -0
  50. data/lib/hanami/cli/generators/app/slice/view.erb +0 -2
  51. data/lib/hanami/cli/generators/app/slice.rb +20 -6
  52. data/lib/hanami/cli/generators/app/slice_context.rb +18 -0
  53. data/lib/hanami/cli/generators/app/view/app_template.html.erb +1 -0
  54. data/lib/hanami/cli/generators/app/view/app_view.erb +10 -0
  55. data/lib/hanami/cli/generators/app/view/slice_template.html.erb +1 -0
  56. data/lib/hanami/cli/generators/app/view/slice_view.erb +10 -0
  57. data/lib/hanami/cli/generators/app/view.rb +89 -0
  58. data/lib/hanami/cli/generators/app/view_context.rb +100 -0
  59. data/lib/hanami/cli/generators/context.rb +72 -5
  60. data/lib/hanami/cli/generators/gem/app/404.html +82 -0
  61. data/lib/hanami/cli/generators/gem/app/500.html +82 -0
  62. data/lib/hanami/cli/generators/gem/app/app_css.erb +5 -0
  63. data/lib/hanami/cli/generators/gem/app/app_js.erb +1 -0
  64. data/lib/hanami/cli/generators/gem/app/app_layout.erb +18 -0
  65. data/lib/hanami/cli/generators/gem/app/assets.js +16 -0
  66. data/lib/hanami/cli/generators/gem/app/dev +8 -0
  67. data/lib/hanami/cli/generators/gem/app/favicon.ico +0 -0
  68. data/lib/hanami/cli/generators/gem/app/gemfile.erb +14 -6
  69. data/lib/hanami/cli/generators/gem/app/gitignore.erb +6 -0
  70. data/lib/hanami/cli/generators/gem/app/helpers.erb +10 -0
  71. data/lib/hanami/cli/generators/gem/app/package.json.erb +8 -0
  72. data/lib/hanami/cli/generators/gem/app/procfile.erb +4 -0
  73. data/lib/hanami/cli/generators/gem/app/puma.erb +39 -7
  74. data/lib/hanami/cli/generators/gem/app/routes.erb +1 -1
  75. data/lib/hanami/cli/generators/gem/app/view.erb +9 -0
  76. data/lib/hanami/cli/generators/gem/app.rb +25 -1
  77. data/lib/hanami/cli/generators/version.rb +14 -2
  78. data/lib/hanami/cli/interactive_system_call.rb +65 -0
  79. data/lib/hanami/cli/naming.rb +25 -0
  80. data/lib/hanami/cli/system_call.rb +8 -2
  81. data/lib/hanami/cli/url.rb +1 -1
  82. data/lib/hanami/cli/version.rb +1 -1
  83. metadata +47 -6
@@ -20,12 +20,20 @@ module Hanami
20
20
  migration_code, migration_name = find_migration(target)
21
21
 
22
22
  if migration_name.nil?
23
- out.puts "==> migration file for target #{target} was not found"
23
+ output = if target
24
+ "==> migration file for target #{target} was not found"
25
+ else
26
+ "==> no migrations to rollback"
27
+ end
28
+
29
+ out.puts output
24
30
  return
25
31
  end
26
32
 
27
33
  measure "database #{database.name} rolled back to #{migration_name}" do
28
34
  database.run_migrations(target: Integer(migration_code))
35
+
36
+ true
29
37
  end
30
38
 
31
39
  run_command Structure::Dump if dump
@@ -33,14 +41,30 @@ module Hanami
33
41
 
34
42
  private
35
43
 
36
- def find_migration(code)
37
- migration = database.applied_migrations.then { |migrations|
44
+ def find_migration(code) # rubocop:disable Metrics/PerceivedComplexity
45
+ applied_migrations = database.applied_migrations
46
+
47
+ return if applied_migrations.empty?
48
+
49
+ # Rollback to initial state if we have only one migration and
50
+ # no target is specified. In this case the rollback target
51
+ # will be the current migration timestamp minus 1
52
+ if applied_migrations.one? && code.nil?
53
+ migration = applied_migrations.first
54
+
55
+ migration_code = Integer(migration.split("_").first) - 1
56
+ migration_name = "initial state"
57
+
58
+ return [migration_code, migration_name]
59
+ end
60
+
61
+ # Otherwise rollback to target or to previous migration
62
+ migration =
38
63
  if code
39
- migrations.detect { |m| m.split("_").first == code }
64
+ applied_migrations.detect { |m| m.split("_").first == code }
40
65
  else
41
- migrations.last
66
+ applied_migrations[-2]
42
67
  end
43
- }
44
68
 
45
69
  return unless migration
46
70
 
@@ -139,7 +139,11 @@ module Hanami
139
139
  @sequel_migrator ||= begin
140
140
  require "sequel"
141
141
  Sequel.extension :migration
142
- Sequel::TimestampMigrator.new(migrator.connection, migrations_path, {})
142
+
143
+ require "rom/sql"
144
+ ROM::SQL.with_gateway(gateway) do
145
+ Sequel::TimestampMigrator.new(migrator.connection, migrations_path, {})
146
+ end
143
147
  end
144
148
  end
145
149
 
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../interactive_system_call"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Commands
8
+ module App
9
+ # @since 2.1.0
10
+ # @api private
11
+ class Dev < App::Command
12
+ # @since 2.1.0
13
+ # @api private
14
+ desc "Start the application in development mode"
15
+
16
+ # @since 2.1.0
17
+ # @api private
18
+ def initialize(
19
+ out:, err:,
20
+ system_call: InteractiveSystemCall.new(out: out, err: err),
21
+ **opts
22
+ )
23
+ super(out: out, err: err, **opts)
24
+
25
+ @system_call = system_call
26
+ end
27
+
28
+ # @since 2.1.0
29
+ # @api private
30
+ def call(**)
31
+ bin, args = executable
32
+ system_call.call(bin, *args)
33
+ end
34
+
35
+ private
36
+
37
+ # @since 2.1.0
38
+ # @api private
39
+ attr_reader :system_call
40
+
41
+ # @since 2.1.0
42
+ # @api private
43
+ def executable
44
+ [::File.join("bin", "dev")]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -3,6 +3,7 @@
3
3
  require "dry/inflector"
4
4
  require "dry/files"
5
5
  require "shellwords"
6
+ require_relative "../../../naming"
6
7
  require_relative "../../../errors"
7
8
 
8
9
  module Hanami
@@ -21,12 +22,25 @@ module Hanami
21
22
  DEFAULT_SKIP_VIEW = false
22
23
  private_constant :DEFAULT_SKIP_VIEW
23
24
 
25
+ DEFAULT_SKIP_TESTS = false
26
+ private_constant :DEFAULT_SKIP_TESTS
27
+
24
28
  argument :name, required: true, desc: "Action name"
25
29
  option :url, required: false, type: :string, desc: "Action URL"
26
30
  option :http, required: false, type: :string, desc: "Action HTTP method"
27
31
  # option :format, required: false, type: :string, default: DEFAULT_FORMAT, desc: "Template format"
28
- # option :skip_view, required: false, type: :boolean, default: DEFAULT_SKIP_VIEW,
29
- # desc: "Skip view and template generation"
32
+ option \
33
+ :skip_view,
34
+ required: false,
35
+ type: :boolean,
36
+ default: DEFAULT_SKIP_VIEW,
37
+ desc: "Skip view and template generation"
38
+ option \
39
+ :skip_tests,
40
+ required: false,
41
+ type: :boolean,
42
+ default: DEFAULT_SKIP_TESTS,
43
+ desc: "Skip test generation"
30
44
  option :slice, required: false, desc: "Slice name"
31
45
 
32
46
  # rubocop:disable Layout/LineLength
@@ -47,33 +61,48 @@ module Hanami
47
61
 
48
62
  # @since 2.0.0
49
63
  # @api private
50
- def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
51
- generator: Generators::App::Action.new(fs: fs, inflector: inflector), **)
64
+ def initialize(
65
+ fs:, inflector:,
66
+ naming: Naming.new(inflector: inflector),
67
+ generator: Generators::App::Action.new(fs: fs, inflector: inflector),
68
+ **opts
69
+ )
70
+ super(fs: fs, inflector: inflector, **opts)
71
+
72
+ @naming = naming
52
73
  @generator = generator
53
- super(fs: fs)
54
74
  end
55
75
 
56
76
  # rubocop:disable Metrics/ParameterLists
57
77
 
58
78
  # @since 2.0.0
59
79
  # @api private
60
- def call(name:, url: nil, http: nil, format: DEFAULT_FORMAT, skip_view: DEFAULT_SKIP_VIEW, slice: nil, **)
80
+ def call(
81
+ name:,
82
+ url: nil,
83
+ http: nil,
84
+ format: DEFAULT_FORMAT,
85
+ skip_view: DEFAULT_SKIP_VIEW,
86
+ skip_tests: DEFAULT_SKIP_TESTS, # rubocop:disable Lint/UnusedMethodArgument
87
+ slice: nil,
88
+ context: nil, **
89
+ )
61
90
  slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
62
- name = inflector.underscore(Shellwords.shellescape(name))
91
+ name = naming.action_name(name)
63
92
  *controller, action = name.split(ACTION_SEPARATOR)
64
93
 
65
94
  if controller.empty?
66
95
  raise InvalidActionNameError.new(name)
67
96
  end
68
97
 
69
- generator.call(app.namespace, controller, action, url, http, format, skip_view, slice)
98
+ generator.call(app.namespace, controller, action, url, http, format, skip_view, slice, context: context)
70
99
  end
71
100
 
72
101
  # rubocop:enable Metrics/ParameterLists
73
102
 
74
103
  private
75
104
 
76
- attr_reader :generator
105
+ attr_reader :naming, :generator
77
106
  end
78
107
  end
79
108
  end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+ require "dry/files"
5
+ require "shellwords"
6
+
7
+ module Hanami
8
+ module CLI
9
+ module Commands
10
+ module App
11
+ module Generate
12
+ # @since 2.1.0
13
+ # @api private
14
+ class Part < App::Command
15
+ DEFAULT_SKIP_TESTS = false
16
+ private_constant :DEFAULT_SKIP_TESTS
17
+
18
+ argument :name, required: true, desc: "Part name"
19
+ option :slice, required: false, desc: "Slice name"
20
+ option \
21
+ :skip_tests,
22
+ required: false,
23
+ type: :boolean,
24
+ default: DEFAULT_SKIP_TESTS,
25
+ desc: "Skip test generation"
26
+
27
+ example [
28
+ %(book (MyApp::Views::Parts::Book)),
29
+ %(book --slice=admin (Admin::Views::Parts::Book)),
30
+ ]
31
+ attr_reader :generator
32
+ private :generator
33
+
34
+ # @since 2.0.0
35
+ # @api private
36
+ def initialize(
37
+ fs:, inflector:,
38
+ generator: Generators::App::Part.new(fs: fs, inflector: inflector),
39
+ **opts
40
+ )
41
+ super(fs: fs, inflector: inflector, **opts)
42
+ @generator = generator
43
+ end
44
+
45
+ # @since 2.0.0
46
+ # @api private
47
+ def call(name:, slice: nil, skip_tests: DEFAULT_SKIP_TESTS, **) # rubocop:disable Lint/UnusedMethodArgument
48
+ slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
49
+
50
+ generator.call(app.namespace, name, slice)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -23,10 +23,13 @@ module Hanami
23
23
 
24
24
  # @since 2.0.0
25
25
  # @api private
26
- def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
27
- generator: Generators::App::Slice.new(fs: fs, inflector: inflector), **)
26
+ def initialize(
27
+ fs:, inflector:,
28
+ generator: Generators::App::Slice.new(fs: fs, inflector: inflector),
29
+ **opts
30
+ )
31
+ super(fs: fs, inflector: inflector, **opts)
28
32
  @generator = generator
29
- super(fs: fs)
30
33
  end
31
34
 
32
35
  # @since 2.0.0
@@ -0,0 +1,56 @@
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.0.0
15
+ # @api private
16
+ class View < App::Command
17
+ # TODO: make this configurable
18
+ DEFAULT_FORMAT = "html"
19
+ private_constant :DEFAULT_FORMAT
20
+
21
+ # TODO: make engine configurable
22
+
23
+ argument :name, required: true, desc: "View name"
24
+ option :slice, required: false, desc: "Slice name"
25
+
26
+ example [
27
+ %(books.index (MyApp::Actions::Books::Index)),
28
+ %(books.index --slice=admin (Admin::Actions::Books::Index)),
29
+ ]
30
+ attr_reader :generator
31
+ private :generator
32
+
33
+ # @since 2.0.0
34
+ # @api private
35
+ def initialize(
36
+ fs:, inflector:,
37
+ generator: Generators::App::View.new(fs: fs, inflector: inflector),
38
+ **opts
39
+ )
40
+ super(fs: fs, inflector: inflector, **opts)
41
+ @generator = generator
42
+ end
43
+
44
+ # @since 2.0.0
45
+ # @api private
46
+ def call(name:, format: DEFAULT_FORMAT, slice: nil, **)
47
+ slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
48
+
49
+ generator.call(app.namespace, name, format, slice)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -7,8 +7,8 @@ module Hanami
7
7
  # @since 2.0.0
8
8
  # @api private
9
9
  module Generate
10
- require_relative "./generate/slice"
11
- require_relative "./generate/action"
10
+ require_relative "generate/slice"
11
+ require_relative "generate/action"
12
12
  end
13
13
  end
14
14
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../../system_call"
4
+
3
5
  module Hanami
4
6
  module CLI
5
7
  module Commands
@@ -18,9 +20,22 @@ module Hanami
18
20
  # @since 2.0.0
19
21
  # @api private
20
22
  class Install < Command
23
+ # @since 2.1.0
24
+ # @api private
25
+ DEFAULT_HEAD = false
26
+ private_constant :DEFAULT_HEAD
27
+
28
+ # @since 2.1.0
29
+ # @api private
21
30
  desc "Install Hanami third-party plugins"
22
31
 
23
- def call(*)
32
+ # @since 2.1.0
33
+ # @api private
34
+ option :head, type: :boolean, desc: "Install head deps", default: DEFAULT_HEAD
35
+
36
+ # @since 2.0.0
37
+ # @api private
38
+ def call(head: DEFAULT_HEAD, **)
24
39
  end
25
40
  end
26
41
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "rack"
4
+ require "hanami/port"
4
5
  require_relative "../app"
5
6
  require_relative "../../server"
6
7
 
@@ -28,11 +29,6 @@ module Hanami
28
29
  # @since 2.0.0
29
30
  # @api private
30
31
  class Server < Command
31
- # @since 2.0.0
32
- # @api private
33
- DEFAULT_PORT = 2300
34
- private_constant :DEFAULT_PORT
35
-
36
32
  # @since 2.0.0
37
33
  # @api private
38
34
  DEFAULT_CONFIG_PATH = "config.ru"
@@ -42,7 +38,7 @@ module Hanami
42
38
 
43
39
  option :host, default: nil, required: false,
44
40
  desc: "The host address to bind to (falls back to the rack handler)"
45
- option :port, default: DEFAULT_PORT, required: false,
41
+ option :port, default: Hanami::Port::DEFAULT, required: false,
46
42
  desc: "The port to run the server on (falls back to the rack handler)"
47
43
  option :config, default: DEFAULT_CONFIG_PATH, required: false, desc: "Rack configuration file"
48
44
  option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :boolean
@@ -50,15 +46,15 @@ module Hanami
50
46
 
51
47
  # @since 2.0.0
52
48
  # @api private
53
- def initialize(server: Hanami::CLI::Server.new)
54
- super()
49
+ def initialize(server: Hanami::CLI::Server.new, **opts)
50
+ super(**opts)
55
51
  @server = server
56
52
  end
57
53
 
58
54
  # @since 2.0.0
59
55
  # @api private
60
- def call(port: DEFAULT_PORT, **kwargs)
61
- server.call(port: port, **kwargs)
56
+ def call(port: Hanami::Port::DEFAULT, **kwargs)
57
+ server.call(port: Hanami::Port[port], **kwargs)
62
58
  end
63
59
 
64
60
  private
@@ -14,14 +14,24 @@ module Hanami
14
14
  base.module_eval do
15
15
  register "version", Commands::App::Version, aliases: ["v", "-v", "--version"]
16
16
  register "install", Commands::App::Install
17
+ register "dev", Commands::App::Dev
17
18
  register "console", Commands::App::Console, aliases: ["c"]
18
19
  register "server", Commands::App::Server, aliases: ["s"]
19
20
  register "routes", Commands::App::Routes
20
21
  register "middleware", Commands::App::Middleware
21
22
 
23
+ if Hanami.bundled?("hanami-assets")
24
+ register "assets" do |prefix|
25
+ prefix.register "compile", Assets::Compile
26
+ prefix.register "watch", Assets::Watch
27
+ end
28
+ end
29
+
22
30
  register "generate", aliases: ["g"] do |prefix|
23
31
  prefix.register "slice", Generate::Slice
24
32
  prefix.register "action", Generate::Action
33
+ prefix.register "view", Generate::View
34
+ prefix.register "part", Generate::Part
25
35
  end
26
36
  end
27
37
  end
@@ -10,66 +10,124 @@ module Hanami
10
10
  # @since 2.0.0
11
11
  # @api private
12
12
  class New < Command
13
+ # @since 2.0.0
14
+ # @api private
13
15
  SKIP_INSTALL_DEFAULT = false
14
16
  private_constant :SKIP_INSTALL_DEFAULT
15
17
 
18
+ # @since 2.1.0
19
+ # @api private
20
+ HEAD_DEFAULT = false
21
+ private_constant :HEAD_DEFAULT
22
+
23
+ # @since 2.1.0
24
+ # @api private
25
+ SKIP_ASSETS_DEFAULT = false
26
+ private_constant :SKIP_ASSETS_DEFAULT
27
+
16
28
  desc "Generate a new Hanami app"
17
29
 
30
+ # @since 2.0.0
31
+ # @api private
18
32
  argument :app, required: true, desc: "App name"
19
33
 
34
+ # @since 2.0.0
35
+ # @api private
20
36
  option :skip_install, type: :boolean, required: false,
21
37
  default: SKIP_INSTALL_DEFAULT,
22
38
  desc: "Skip app installation (Bundler, third-party Hanami plugins)"
23
39
 
40
+ # @since 2.1.0
41
+ # @api private
42
+ option :head, type: :boolean, required: false,
43
+ default: HEAD_DEFAULT,
44
+ desc: "Use Hanami HEAD version (from GitHub `main` branches)"
45
+
46
+ # @since 2.1.0
47
+ # @api private
48
+ option :skip_assets, type: :boolean, required: false,
49
+ default: SKIP_ASSETS_DEFAULT,
50
+ desc: "Skip assets"
51
+
52
+ # rubocop:disable Layout/LineLength
24
53
  example [
25
- "bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace", # rubocop:disable Layout/LineLength
26
- "bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation"
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"
27
58
  ]
59
+ # rubocop:enable Layout/LineLength
60
+
61
+ # rubocop:disable Metrics/ParameterLists
28
62
 
29
63
  # @since 2.0.0
30
64
  # @api private
31
65
  def initialize(
32
- fs: Hanami::CLI::Files.new,
33
- inflector: Dry::Inflector.new,
66
+ fs:, inflector:,
34
67
  bundler: CLI::Bundler.new(fs: fs),
35
68
  generator: Generators::Gem::App.new(fs: fs, inflector: inflector),
36
- **other
69
+ system_call: SystemCall.new,
70
+ **opts
37
71
  )
72
+ super(fs: fs, inflector: inflector, **opts)
38
73
  @bundler = bundler
39
74
  @generator = generator
40
- super(fs: fs, inflector: inflector, **other)
75
+ @system_call = system_call
41
76
  end
42
77
 
78
+ # rubocop:enable Metrics/ParameterLists
79
+
80
+ # rubocop:disable Metrics/AbcSize
81
+
43
82
  # @since 2.0.0
44
83
  # @api private
45
- def call(app:, skip_install: SKIP_INSTALL_DEFAULT, **)
84
+ def call(app:, head: HEAD_DEFAULT, skip_install: SKIP_INSTALL_DEFAULT, skip_assets: SKIP_ASSETS_DEFAULT, **)
46
85
  app = inflector.underscore(app)
47
86
 
48
87
  raise PathAlreadyExistsError.new(app) if fs.exist?(app)
49
88
 
50
89
  fs.mkdir(app)
51
90
  fs.chdir(app) do
52
- generator.call(app) do
91
+ context = Generators::Context.new(inflector, app, head: head, skip_assets: skip_assets)
92
+ generator.call(app, context: context) do
53
93
  if skip_install
54
94
  out.puts "Skipping installation, please enter `#{app}' directory and run `bundle exec hanami install'"
55
95
  else
56
96
  out.puts "Running Bundler install..."
57
97
  bundler.install!
98
+
99
+ unless skip_assets
100
+ out.puts "Running NPM install..."
101
+ system_call.call("npm", ["install"]).tap do |result|
102
+ unless result.successful?
103
+ puts "NPM ERROR:"
104
+ puts(result.err.lines.map { |line| line.prepend(" ") })
105
+ end
106
+ end
107
+ end
108
+
58
109
  out.puts "Running Hanami install..."
59
- run_install_commmand!
110
+ run_install_command!(head: head)
60
111
  end
61
112
  end
62
113
  end
63
114
  end
115
+ # rubocop:enable Metrics/AbcSize
64
116
 
65
117
  private
66
118
 
67
119
  attr_reader :bundler
68
120
  attr_reader :generator
121
+ attr_reader :system_call
69
122
 
70
- def run_install_commmand!
71
- bundler.exec("hanami install").tap do |result|
72
- raise HanamiInstallError.new(result.err) unless result.successful?
123
+ def run_install_command!(head:)
124
+ head_flag = head ? " --head" : ""
125
+ bundler.exec("hanami install#{head_flag}").tap do |result|
126
+ if result.successful?
127
+ bundler.exec("check").successful? || bundler.exec("install")
128
+ else
129
+ raise HanamiInstallError.new(result.err)
130
+ end
73
131
  end
74
132
  end
75
133
  end