hanami-cli 2.0.0.beta2 → 2.0.0.beta4

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/CHANGELOG.md +24 -0
  4. data/Gemfile +0 -1
  5. data/hanami-cli.gemspec +1 -1
  6. data/lib/hanami/cli/command.rb +4 -1
  7. data/lib/hanami/cli/commands/app/command.rb +4 -2
  8. data/lib/hanami/cli/commands/app/console.rb +22 -12
  9. data/lib/hanami/cli/commands/app/db/create.rb +1 -0
  10. data/lib/hanami/cli/commands/app/generate/action.rb +17 -4
  11. data/lib/hanami/cli/commands/app/generate/slice.rb +14 -8
  12. data/lib/hanami/cli/commands/app/install.rb +2 -0
  13. data/lib/hanami/cli/commands/app/{middlewares.rb → middleware.rb} +11 -6
  14. data/lib/hanami/cli/commands/app/routes.rb +9 -4
  15. data/lib/hanami/cli/commands/app/server.rb +5 -2
  16. data/lib/hanami/cli/commands/app/version.rb +2 -0
  17. data/lib/hanami/cli/commands/app.rb +2 -2
  18. data/lib/hanami/cli/commands/db/utils/database.rb +8 -1
  19. data/lib/hanami/cli/commands/db/utils/postgres.rb +5 -0
  20. data/lib/hanami/cli/commands/gem/new.rb +20 -8
  21. data/lib/hanami/cli/commands/gem/version.rb +2 -0
  22. data/lib/hanami/cli/commands.rb +2 -3
  23. data/lib/hanami/cli/files.rb +50 -0
  24. data/lib/hanami/cli/generators/app/action/action.erb +2 -2
  25. data/lib/hanami/cli/generators/app/action/slice_action.erb +2 -2
  26. data/lib/hanami/cli/generators/app/action/template.html.erb +1 -1
  27. data/lib/hanami/cli/generators/app/action/view.erb +2 -2
  28. data/lib/hanami/cli/generators/app/action.rb +27 -22
  29. data/lib/hanami/cli/generators/app/action_context.rb +3 -3
  30. data/lib/hanami/cli/generators/app/slice/action.erb +2 -2
  31. data/lib/hanami/cli/generators/app/slice/entities.erb +1 -1
  32. data/lib/hanami/cli/generators/app/slice/repository.erb +2 -2
  33. data/lib/hanami/cli/generators/app/slice/routes.erb +1 -1
  34. data/lib/hanami/cli/generators/app/slice/slice.erb +1 -1
  35. data/lib/hanami/cli/generators/app/slice/view.erb +2 -2
  36. data/lib/hanami/cli/generators/app/slice.rb +16 -16
  37. data/lib/hanami/cli/generators/app/slice_context.rb +5 -5
  38. data/lib/hanami/cli/generators/context.rb +2 -2
  39. data/lib/hanami/cli/generators/gem/app/action.erb +1 -1
  40. data/lib/hanami/cli/generators/gem/app/app.erb +1 -1
  41. data/lib/hanami/cli/generators/gem/app/gemfile.erb +5 -1
  42. data/lib/hanami/cli/generators/gem/app/puma.erb +15 -0
  43. data/lib/hanami/cli/generators/gem/app/readme.erb +1 -1
  44. data/lib/hanami/cli/generators/gem/app/routes.erb +2 -4
  45. data/lib/hanami/cli/generators/gem/app/settings.erb +1 -3
  46. data/lib/hanami/cli/generators/gem/app/types.erb +1 -1
  47. data/lib/hanami/cli/generators/gem/app/validator.erb +1 -1
  48. data/lib/hanami/cli/generators/gem/app.rb +1 -0
  49. data/lib/hanami/cli/middleware_stack_inspector.rb +3 -3
  50. data/lib/hanami/cli/version.rb +1 -1
  51. data/lib/hanami/console/context.rb +3 -2
  52. metadata +9 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 511daafabba67025eb83dd27f06c62a9809ab89c8901445e5c00f15bbf8fd8ca
4
- data.tar.gz: 7d303b0c72fb47c2cf0ba107af0e6899d83b14b580565f4b30c43f4bc87ab4f9
3
+ metadata.gz: 9e5786ce0557b00c863e145560473293d2d172f96c4bcdd10e7dcc32484a32a3
4
+ data.tar.gz: 036fb11ad8f805173889beb755daa50d8de1da678d74e853f9f8ddf65fa3dde1
5
5
  SHA512:
6
- metadata.gz: 716e634d4c572a09cd9954484d9dafc6affaafc13d03ec1e79703436b3efd305d1823dc4d35e92207b5388ebc33aef9f9ba603089cf73b3aec904957fe760bd6
7
- data.tar.gz: e7d545d1cd6bfabcc5b4305b755c319c06a4e688e74a4639765c8f0026aac6b039d3459d670014a096ebd726b4a3192f694e273285341698b52a02b0762ae4a4
6
+ metadata.gz: 3bde684529fa1ae28f86bee5aa8b9fb42297a7e112ff272751c0a234f5cb22cb13ea6a3bf46d580a56d9804225fd2d0834e131eb53fd60184cb0d6096f8f2a08
7
+ data.tar.gz: de2d099cfe069f4fd6c85aa23c5ce60f0133e9a6c1858c9f237f9c104ee5f2e56ab11965a7b979de754d6cdcefc8cbb9b5abfc9b0acc58cda2e2402cb570c546
data/.rubocop.yml CHANGED
@@ -22,6 +22,8 @@ Style/BlockDelimiters:
22
22
  Enabled: false
23
23
  Style/CommentedKeyword:
24
24
  Enabled: false
25
+ Style/EmptyHeredoc:
26
+ Enabled: false
25
27
  Style/LambdaCall:
26
28
  Enabled: false
27
29
  Style/TrailingCommaInArguments:
data/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  Hanami Command Line Interface
4
4
 
5
+ ## v2.0.0.beta4 - 2022-10-24
6
+
7
+ ### Changed
8
+
9
+ - [Sean Collins] Show output when generating files (e.g. in `hanami new`) (#49)
10
+ - [Luca Guidi] Advertise Bundler and Hanami install steps when running `hanami new` (#54)
11
+
12
+ ## v2.0.0.beta3 - 2022-09-21
13
+
14
+ ### Added
15
+
16
+ - [Luca Guidi] New applications to support Puma server out of the box. Add the `puma` gem to `Gemfile` and generate `config/puma.rb`.
17
+ - [Luca Guidi] New applications to support code reloading for `hanami server` via `hanami-reloader`. This gem is added to app's `Gemfile`.
18
+ - [Marc Busqué] Introduce code reloading for `hanami console` via `#reload` method to be invoked within the console context.
19
+
20
+ ### Fixed
21
+
22
+ - [Luca Guidi] Respect plural when generating code via `hanami new` and `hanami generate`. Example: `hanami new code_insights` => `CodeInsights` instead of `CodeInsight`
23
+ - [Luca Guidi] Ensure `hanami generate action` to not crash when invoked with non-RESTful action names. Example: `hanami generate action talent.apply`
24
+
25
+ ### Changed
26
+
27
+ - [Piotr Solnica] `hanami generate action` to add routes to `config/routes.rb` without the `define` block context. See https://github.com/hanami/hanami/pull/1208
28
+
5
29
  ## v2.0.0.beta2 - 2022-08-16
6
30
 
7
31
  ### Added
data/Gemfile CHANGED
@@ -8,7 +8,6 @@ unless ENV["CI"]
8
8
  gem "yard", require: false
9
9
  end
10
10
 
11
- gem "dry-files", require: false, github: "dry-rb/dry-files", branch: :main
12
11
  gem "hanami", require: false, github: "hanami/hanami", branch: :main
13
12
  gem "hanami-router", github: "hanami/router", branch: :main
14
13
 
data/hanami-cli.gemspec CHANGED
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
33
33
  spec.add_dependency "bundler", "~> 2.1"
34
34
  spec.add_dependency "rake", "~> 13.0"
35
35
  spec.add_dependency "dry-cli", "~> 0.7"
36
- spec.add_dependency "dry-files", "~> 0.2", ">= 0.2.0"
36
+ spec.add_dependency "dry-files", "~> 0.3", ">= 0.3.0"
37
37
  spec.add_dependency "dry-inflector", "~> 0.2"
38
38
 
39
39
  spec.add_development_dependency "rspec", "~> 3.9"
@@ -7,9 +7,10 @@ require "dry/inflector"
7
7
  module Hanami
8
8
  module CLI
9
9
  class Command < Dry::CLI::Command
10
- def initialize(out: $stdout, fs: Dry::Files.new, inflector: Dry::Inflector.new)
10
+ def initialize(out: $stdout, err: $stderr, fs: Dry::Files.new, inflector: Dry::Inflector.new)
11
11
  super()
12
12
  @out = out
13
+ @err = err
13
14
  @fs = fs
14
15
  @inflector = inflector
15
16
  end
@@ -18,6 +19,8 @@ module Hanami
18
19
 
19
20
  attr_reader :out
20
21
 
22
+ attr_reader :err
23
+
21
24
  attr_reader :fs
22
25
 
23
26
  attr_reader :inflector
@@ -8,15 +8,17 @@ module Hanami
8
8
  module Commands
9
9
  module App
10
10
  class Command < Hanami::CLI::Command
11
+ ACTION_SEPARATOR = "."
12
+
11
13
  module Environment
12
- def call(**opts)
14
+ def call(*args, **opts)
13
15
  env = opts[:env]
14
16
 
15
17
  hanami_env = env ? env.to_s : ENV.fetch("HANAMI_ENV", "development")
16
18
 
17
19
  ENV["HANAMI_ENV"] = hanami_env
18
20
 
19
- super(**opts)
21
+ super(*args, **opts)
20
22
  end
21
23
  end
22
24
 
@@ -10,37 +10,47 @@ module Hanami
10
10
  module App
11
11
  # @api public
12
12
  class Console < App::Command
13
- REPLS = {
13
+ ENGINES = {
14
14
  "pry" => -> (*args) {
15
15
  begin
16
16
  require "hanami/cli/repl/pry"
17
17
  Repl::Pry.new(*args)
18
- rescue LoadError; end # rubocop:disable Lint/SuppressedException
18
+ rescue LoadError # rubocop:disable Lint/SuppressedException
19
+ end
19
20
  },
20
21
  "irb" => -> (*args) {
21
22
  require "hanami/cli/repl/irb"
22
23
  Repl::Irb.new(*args)
23
24
  },
24
25
  }.freeze
26
+ private_constant :ENGINES
25
27
 
26
- desc "App REPL"
28
+ DEFAULT_ENGINE = "irb"
29
+ private_constant :DEFAULT_ENGINE
27
30
 
28
- option :env, required: false, desc: "Application environment"
29
- option :repl, required: false, desc: "REPL gem that should be used ('pry' or 'irb')"
31
+ desc "Start app console (REPL)"
32
+
33
+ option :engine, required: false, desc: "Console engine", values: ENGINES.keys
30
34
 
31
35
  # @api private
32
- def call(repl: nil, **opts)
33
- engine = resolve_engine(repl, opts)
34
- engine.start
36
+ def call(engine: nil, **opts)
37
+ console_engine = resolve_engine(engine, opts)
38
+
39
+ if console_engine.nil?
40
+ err.puts "`#{engine}' is not bundled. Please run `bundle add #{engine}' and retry."
41
+ exit(1)
42
+ end
43
+
44
+ console_engine.start
35
45
  end
36
46
 
37
47
  private
38
48
 
39
- def resolve_engine(repl, opts)
40
- if repl
41
- REPLS.fetch(repl).(app, opts)
49
+ def resolve_engine(engine, opts)
50
+ if engine
51
+ ENGINES.fetch(engine).(app, opts)
42
52
  else
43
- REPLS.map { |(_, loader)| loader.(app, opts) }.compact.first
53
+ ENGINES.map { |(_, loader)| loader.(app, opts) }.compact.first
44
54
  end
45
55
  end
46
56
  end
@@ -15,6 +15,7 @@ module Hanami
15
15
  out.puts "=> database #{database.name} created"
16
16
  else
17
17
  out.puts "=> failed to create database #{database.name}"
18
+ exit $?.exitstatus
18
19
  end
19
20
  end
20
21
  end
@@ -28,7 +28,23 @@ module Hanami
28
28
  # desc: "Skip view and template generation"
29
29
  option :slice, required: false, desc: "Slice name"
30
30
 
31
- def initialize(fs: Dry::Files.new, inflector: Dry::Inflector.new,
31
+ # rubocop:disable Layout/LineLength
32
+ example [
33
+ %(books.index # GET /books to: "books.index" (MyApp::Actions::Books::Index)),
34
+ %(books.new # GET /books/new to: "books.new" (MyApp::Actions::Books::New)),
35
+ %(books.create # POST /books to: "books.create" (MyApp::Actions::Books::Create)),
36
+ %(books.edit # GET /books/:id/edit to: "books.edit" (MyApp::Actions::Books::Edit)),
37
+ %(books.update # PATCH /books/:id to: "books.update" (MyApp::Actions::Books::Update)),
38
+ %(books.show # GET /books/:id to: "books.show" (MyApp::Actions::Books::Show)),
39
+ %(books.destroy # DELETE /books/:id to: "books.destroy" (MyApp::Actions::Books::Destroy)),
40
+ %(books.sale # GET /books/sale to: "books.sale" (MyApp::Actions::Books::Sale)),
41
+ %(sessions.new --url=/login # GET /login to: "sessions.new" (MyApp::Actions::Sessions::New)),
42
+ %(authors.update --http=put # PUT /authors/:id to: "authors.update" (MyApp::Actions::Authors::Update)),
43
+ %(users.index --slice=admin # GET /admin/users to: "users.index" (Admin::Actions::Users::Update))
44
+ ]
45
+ # rubocop:enable Layout/LineLength
46
+
47
+ def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
32
48
  generator: Generators::App::Action.new(fs: fs, inflector: inflector), **)
33
49
  @generator = generator
34
50
  super(fs: fs)
@@ -50,9 +66,6 @@ module Hanami
50
66
 
51
67
  private
52
68
 
53
- ACTION_SEPARATOR = "."
54
- private_constant :ACTION_SEPARATOR
55
-
56
69
  attr_reader :generator
57
70
  end
58
71
  end
@@ -5,6 +5,7 @@ require "hanami/cli/generators/app/slice"
5
5
  require "dry/inflector"
6
6
  require "dry/files"
7
7
  require "shellwords"
8
+ require "hanami/cli/files"
8
9
  require "hanami/cli/url"
9
10
 
10
11
  module Hanami
@@ -14,22 +15,27 @@ module Hanami
14
15
  module Generate
15
16
  class Slice < Command
16
17
  argument :name, required: true, desc: "The slice name"
17
- option :url_prefix, required: false, type: :string, desc: "The slice URL prefix"
18
+ option :url, required: false, type: :string, desc: "The slice URL prefix"
18
19
 
19
- def initialize(fs: Dry::Files.new, inflector: Dry::Inflector.new,
20
+ example [
21
+ "admin # Admin slice (/admin URL prefix)",
22
+ "users --url=/u # Users slice (/u URL prefix)",
23
+ ]
24
+
25
+ def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
20
26
  generator: Generators::App::Slice.new(fs: fs, inflector: inflector), **)
21
27
  @generator = generator
22
28
  super(fs: fs)
23
29
  end
24
30
 
25
- def call(name:, url_prefix: nil, **)
31
+ def call(name:, url: nil, **)
26
32
  require "hanami/setup"
27
33
 
28
34
  app = inflector.underscore(Hanami.app.namespace)
29
35
  name = inflector.underscore(Shellwords.shellescape(name))
30
- url_prefix = sanitize_url_prefix(name, url_prefix)
36
+ url = sanitize_url_prefix(name, url)
31
37
 
32
- generator.call(app, name, url_prefix)
38
+ generator.call(app, name, url)
33
39
  end
34
40
 
35
41
  private
@@ -39,14 +45,14 @@ module Hanami
39
45
 
40
46
  attr_reader :generator
41
47
 
42
- def sanitize_url_prefix(name, url_prefix)
43
- result = url_prefix
48
+ def sanitize_url_prefix(name, url)
49
+ result = url
44
50
  result = inflector.underscore(Shellwords.shellescape(result)) unless result.nil?
45
51
 
46
52
  result ||= DEFAULT_URL_PREFIX + name
47
53
  CLI::URL.call(result)
48
54
  rescue ArgumentError
49
- raise ArgumentError.new("invalid URL prefix: `#{url_prefix}'")
55
+ raise ArgumentError.new("invalid URL prefix: `#{url}'")
50
56
  end
51
57
 
52
58
  def valid_url?(url)
@@ -7,6 +7,8 @@ module Hanami
7
7
  module Commands
8
8
  module App
9
9
  class Install < Command
10
+ desc "Install Hanami third-party plugins"
11
+
10
12
  def call(*)
11
13
  end
12
14
  end
@@ -7,30 +7,35 @@ module Hanami
7
7
  module CLI
8
8
  module Commands
9
9
  module App
10
- # List registered middlewares in the app router
10
+ # List registered middleware in the app router
11
11
  #
12
- # It outputs middlewares registered along with the paths where they
12
+ # It outputs middleware registered along with the paths where they
13
13
  # apply:
14
14
  #
15
15
  # ```
16
- # $ hanami middlewares
16
+ # $ bundle exec hanami middleware
17
17
  # / Rack::Session::Cookie
18
18
  # ```
19
19
  #
20
20
  # Given arguments can be inspected:
21
21
  #
22
22
  # ```
23
- # $ hanami middlewares --with-arguments
23
+ # $ bundle exec hanami middleware --with-arguments
24
24
  # / Rack::Session::Cookie args: [{:secret=>"foo"}]
25
25
  # ```
26
- class Middlewares < Hanami::CLI::Command
27
- desc "List all the registered middlewares"
26
+ class Middleware < Hanami::CLI::Command
27
+ desc "Print app Rack middleware stack"
28
28
 
29
29
  DEFAULT_WITH_ARGUMENTS = false
30
30
 
31
31
  option :with_arguments, default: DEFAULT_WITH_ARGUMENTS, required: false,
32
32
  desc: "Include inspected arguments", type: :boolean
33
33
 
34
+ example [
35
+ "middleware # Print app Rack middleware stack",
36
+ "middleware --with-arguments # Print app Rack middleware stack, including initialize arguments",
37
+ ]
38
+
34
39
  # @api private
35
40
  def call(with_arguments: DEFAULT_WITH_ARGUMENTS)
36
41
  require "hanami/prepare"
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami"
4
- require "hanami/router/inspector"
5
4
 
6
5
  module Hanami
7
6
  module CLI
@@ -12,14 +11,14 @@ module Hanami
12
11
  # All the formatters available from `hanami-router` are available:
13
12
  #
14
13
  # ```
15
- # $ hanami routes --format=csv
14
+ # $ bundle exec hanami routes --format=csv
16
15
  # ```
17
16
  #
18
17
  # Experimental: You can also use a custom formatter registered in the
19
18
  # application container. You can identify it by its key:
20
19
  #
21
20
  # ```
22
- # $ hanami routes --format=custom_routes_formatter
21
+ # $ bundle exec hanami routes --format=custom_routes_formatter
23
22
  # ```
24
23
  class Routes < Hanami::CLI::Command
25
24
  DEFAULT_FORMAT = "human_friendly"
@@ -31,15 +30,21 @@ module Hanami
31
30
  ].freeze
32
31
  private_constant :VALID_FORMATS
33
32
 
34
- desc "Inspect application"
33
+ desc "Print app routes"
35
34
 
36
35
  option :format,
37
36
  default: DEFAULT_FORMAT,
38
37
  required: false,
39
38
  desc: "Output format"
40
39
 
40
+ example [
41
+ "routes # Print app routes",
42
+ "routes --format=csv # Print app routes, using CSV format",
43
+ ]
44
+
41
45
  # @api private
42
46
  def call(format: DEFAULT_FORMAT, **)
47
+ require "hanami/router/inspector"
43
48
  require "hanami/prepare"
44
49
  inspector = Hanami::Router::Inspector.new(formatter: resolve_formatter(format))
45
50
  app.router(inspector: inspector)
@@ -28,13 +28,16 @@ module Hanami
28
28
  DEFAULT_PORT = 2300
29
29
  private_constant :DEFAULT_PORT
30
30
 
31
- desc "Start Hanami server"
31
+ DEFAULT_CONFIG_PATH = "config.ru"
32
+ private_constant :DEFAULT_CONFIG_PATH
33
+
34
+ desc "Start Hanami app server"
32
35
 
33
36
  option :host, default: nil, required: false,
34
37
  desc: "The host address to bind to (falls back to the rack handler)"
35
38
  option :port, default: DEFAULT_PORT, required: false,
36
39
  desc: "The port to run the server on (falls back to the rack handler)"
37
- option :config, default: "config.ru", required: false, desc: "Rack configuration file"
40
+ option :config, default: DEFAULT_CONFIG_PATH, required: false, desc: "Rack configuration file"
38
41
  option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :boolean
39
42
  option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :boolean
40
43
 
@@ -7,6 +7,8 @@ module Hanami
7
7
  module Commands
8
8
  module App
9
9
  class Version < Command
10
+ desc "Print Hanami app version"
11
+
10
12
  def call(*)
11
13
  require "hanami/version"
12
14
  out.puts "v#{Hanami::VERSION}"
@@ -10,7 +10,7 @@ module Hanami
10
10
  require_relative "app/server"
11
11
  require_relative "app/routes"
12
12
  require_relative "app/generate"
13
- require_relative "app/middlewares"
13
+ require_relative "app/middleware"
14
14
  # require_relative "app/db/create"
15
15
  # require_relative "app/db/create_migration"
16
16
  # require_relative "app/db/drop"
@@ -30,7 +30,7 @@ module Hanami
30
30
  register "console", Commands::App::Console, aliases: ["c"]
31
31
  register "server", Commands::App::Server, aliases: ["s"]
32
32
  register "routes", Commands::App::Routes
33
- register "middlewares", Commands::App::Middlewares
33
+ register "middleware", Commands::App::Middleware
34
34
 
35
35
  register "generate", aliases: ["g"] do |prefix|
36
36
  prefix.register "slice", Generate::Slice
@@ -30,7 +30,14 @@ module Hanami
30
30
  }.freeze
31
31
 
32
32
  def self.[](app)
33
- config = DatabaseConfig.new(app.settings.database_url)
33
+ database_url =
34
+ if app.key?(:settings) && app[:settings].respond_to?(:database_url)
35
+ app[:settings].database_url
36
+ else
37
+ ENV.fetch("DATABASE_URL")
38
+ end
39
+
40
+ config = DatabaseConfig.new(database_url)
34
41
 
35
42
  resolver = SCHEME_MAP.fetch(config.db_type) do
36
43
  raise "#{config.db_type} is not a supported db scheme"
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "shellwords"
4
+ require "open3"
4
5
  require_relative "database"
5
6
 
6
7
  module Hanami
@@ -10,6 +11,10 @@ module Hanami
10
11
  module Utils
11
12
  class Postgres < Database
12
13
  def create_command
14
+ existing_stdout, status = Open3.capture2(cli_env_vars, "psql -t -c '\\l #{escaped_name}'")
15
+
16
+ return true if status.success? && existing_stdout.include?(escaped_name)
17
+
13
18
  system(cli_env_vars, "createdb #{escaped_name}")
14
19
  end
15
20
 
@@ -4,7 +4,7 @@ require "hanami/cli/command"
4
4
  require "hanami/cli/bundler"
5
5
  require "hanami/cli/command_line"
6
6
  require "hanami/cli/generators/gem/app"
7
- require "dry/files"
7
+ require "hanami/cli/files"
8
8
  require "dry/inflector"
9
9
 
10
10
  module Hanami
@@ -12,17 +12,25 @@ module Hanami
12
12
  module Commands
13
13
  module Gem
14
14
  class New < Command
15
- SKIP_BUNDLE_DEFAULT = false
16
- private_constant :SKIP_BUNDLE_DEFAULT
15
+ SKIP_INSTALL_DEFAULT = false
16
+ private_constant :SKIP_INSTALL_DEFAULT
17
+
18
+ desc "Generate a new Hanami app"
17
19
 
18
20
  argument :app, required: true, desc: "App name"
19
21
 
20
- option :skip_bundle, type: :boolean, required: false,
21
- default: SKIP_BUNDLE_DEFAULT, desc: "Skip bundle install"
22
+ option :skip_install, type: :boolean, required: false,
23
+ default: SKIP_INSTALL_DEFAULT,
24
+ desc: "Skip app installation (Bundler, third-party Hanami plugins)"
25
+
26
+ example [
27
+ "bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace", # rubocop:disable Layout/LineLength
28
+ "bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation"
29
+ ]
22
30
 
23
31
  # rubocop:disable Metrics/ParameterLists
24
32
  def initialize(
25
- fs: Dry::Files.new,
33
+ fs: Hanami::CLI::Files.new,
26
34
  inflector: Dry::Inflector.new,
27
35
  bundler: CLI::Bundler.new(fs: fs),
28
36
  command_line: CLI::CommandLine.new(bundler: bundler),
@@ -36,14 +44,18 @@ module Hanami
36
44
  end
37
45
  # rubocop:enable Metrics/ParameterLists
38
46
 
39
- def call(app:, skip_bundle: SKIP_BUNDLE_DEFAULT, **)
47
+ def call(app:, skip_install: SKIP_INSTALL_DEFAULT, **)
40
48
  app = inflector.underscore(app)
41
49
 
42
50
  fs.mkdir(app)
43
51
  fs.chdir(app) do
44
52
  generator.call(app) do
45
- unless skip_bundle
53
+ if skip_install
54
+ out.puts "Skipping installation, please enter `#{app}' directory and run `bundle exec hanami install'"
55
+ else
56
+ out.puts "Running Bundler install..."
46
57
  bundler.install!
58
+ out.puts "Running Hanami install..."
47
59
  run_install_commmand!
48
60
  end
49
61
  end
@@ -7,6 +7,8 @@ module Hanami
7
7
  module Commands
8
8
  module Gem
9
9
  class Version < Command
10
+ desc "Hanami version"
11
+
10
12
  def call(*)
11
13
  version = detect_version
12
14
  out.puts "v#{version}"
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami"
4
-
5
3
  module Hanami
6
4
  module CLI
7
5
  # Returns true if the CLI is being called from inside an Hanami app.
@@ -12,7 +10,8 @@ module Hanami
12
10
  # @api public
13
11
  # @since 2.0.0
14
12
  def self.within_hanami_app?
15
- !!Hanami.app_path
13
+ File.exist?("config/app.rb") ||
14
+ File.exist?("app.rb")
16
15
  end
17
16
 
18
17
  module Commands
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hanami
4
+ module CLI
5
+ class Files < Dry::Files
6
+ def initialize(out: $stdout, **args)
7
+ super(**args)
8
+ @out = out
9
+ end
10
+
11
+ def write(path, *content)
12
+ already_exists = exist?(path)
13
+ super
14
+ if already_exists
15
+ updated(path)
16
+ else
17
+ created(path)
18
+ end
19
+ end
20
+
21
+ def mkdir(path)
22
+ unless exist?(path)
23
+ super
24
+ created("#{path}/")
25
+ end
26
+ end
27
+
28
+ def chdir(path, &blk)
29
+ within_folder(path)
30
+ super
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :out
36
+
37
+ def updated(path)
38
+ out.puts "Updated #{path}"
39
+ end
40
+
41
+ def created(path)
42
+ out.puts "Created #{path}"
43
+ end
44
+
45
+ def within_folder(path)
46
+ out.puts "-> Within #{path}/"
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= classified_app_name %>
3
+ module <%= camelized_app_name %>
4
4
  module Actions
5
5
  <%= module_controller_declaration %>
6
- <%= module_controller_offset %>class <%= classified_action_name %> < <%= classified_app_name %>::Action
6
+ <%= module_controller_offset %>class <%= camelized_action_name %> < <%= camelized_app_name %>::Action
7
7
  <%= module_controller_offset %> def handle(*, response)
8
8
  <%= module_controller_offset %> response.body = self.class.name
9
9
  <%= module_controller_offset %> end
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= classified_slice_name %>
3
+ module <%= camelized_slice_name %>
4
4
  module Actions
5
5
  <%= module_controller_declaration %>
6
- <%= module_controller_offset %>class <%= classified_action_name %> < <%= classified_slice_name %>::Action
6
+ <%= module_controller_offset %>class <%= camelized_action_name %> < <%= camelized_slice_name %>::Action
7
7
  <%= module_controller_offset %> def handle(*, response)
8
8
  <%= module_controller_offset %> response.body = self.class.name
9
9
  <%= module_controller_offset %> end
@@ -1,2 +1,2 @@
1
- <h1><%= classified_slice_name %>::Views::<%= classified_controller_name %>::<%= classified_action_name %></h1>
1
+ <h1><%= camelized_slice_name %>::Views::<%= camelized_controller_name %>::<%= camelized_action_name %></h1>
2
2
  <h2><%= template_path %></h2>
@@ -3,10 +3,10 @@
3
3
 
4
4
  require "<%= underscored_slice_name %>/view"
5
5
 
6
- module <%= classified_slice_name %>
6
+ module <%= camelized_slice_name %>
7
7
  module Views
8
8
  <%= module_controller_declaration %>
9
- <%= module_controller_offset %>class <%= classified_action_name %> < <%= classified_slice_name %>::View
9
+ <%= module_controller_offset %>class <%= camelized_action_name %> < <%= camelized_slice_name %>::View
10
10
  <%= module_controller_offset %>end
11
11
  <%= module_controller_end %>
12
12
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "erb"
4
4
  require "dry/files"
5
+ require "hanami/cli/files"
5
6
  require "hanami/cli/generators/app/action_context"
6
7
  require "hanami/cli/url"
7
8
 
@@ -42,16 +43,19 @@ module Hanami
42
43
  private_constant :ROUTE_RESTFUL_HTTP_METHODS
43
44
 
44
45
  ROUTE_RESTFUL_URL_SUFFIXES = {
45
- "index" => "",
46
- "new" => "/new",
47
- "create" => "",
48
- "edit" => "/:id/edit",
49
- "update" => "/:id",
50
- "show" => "/:id",
51
- "destroy" => "/:id"
46
+ "index" => [],
47
+ "new" => ["new"],
48
+ "create" => [],
49
+ "edit" => [":id", "edit"],
50
+ "update" => [":id"],
51
+ "show" => [":id"],
52
+ "destroy" => [":id"]
52
53
  }.freeze
53
54
  private_constant :ROUTE_RESTFUL_URL_SUFFIXES
54
55
 
56
+ PATH_SEPARATOR = "/"
57
+ private_constant :PATH_SEPARATOR
58
+
55
59
  attr_reader :fs
56
60
 
57
61
  attr_reader :inflector
@@ -66,24 +70,22 @@ module Hanami
66
70
  route(controller, action, url, http)
67
71
  )
68
72
 
69
- fs.chdir(slice_directory) do
70
- fs.mkdir(directory = fs.join("actions", controller))
71
- fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
72
-
73
- # unless skip_view
74
- # fs.mkdir(directory = fs.join("views", controller))
75
- # fs.write(fs.join(directory, "#{action}.rb"), t("view.erb", context))
76
- #
77
- # fs.mkdir(directory = fs.join("templates", controller))
78
- # fs.write(fs.join(directory, "#{action}.#{format}.erb"), t(template_format(format), context))
79
- # end
80
- end
73
+ fs.mkdir(directory = fs.join(slice_directory, "actions", controller))
74
+ fs.write(fs.join(directory, "#{action}.rb"), t("slice_action.erb", context))
75
+
76
+ # unless skip_view
77
+ # fs.mkdir(directory = fs.join(slice_directory, "views", controller))
78
+ # fs.write(fs.join(directory, "#{action}.rb"), t("view.erb", context))
79
+ #
80
+ # fs.mkdir(directory = fs.join(slice_directory, "templates", controller))
81
+ # fs.write(fs.join(directory, "#{action}.#{format}.erb"), t(template_format(format), context))
82
+ # end
81
83
  end
82
84
 
83
85
  def generate_for_app(controller, action, url, http, _format, _skip_view, context)
84
- fs.inject_line_at_block_bottom(
86
+ fs.inject_line_at_class_bottom(
85
87
  fs.join("config", "routes.rb"),
86
- /define/,
88
+ "class Routes",
87
89
  route(controller, action, url, http)
88
90
  )
89
91
 
@@ -120,7 +122,10 @@ module Hanami
120
122
  alias_method :t, :template
121
123
 
122
124
  def route_url(controller, action, url)
123
- CLI::URL.call(url || ("/#{controller.join('/')}" + ROUTE_RESTFUL_URL_SUFFIXES.fetch(action)))
125
+ action = ROUTE_RESTFUL_URL_SUFFIXES.fetch(action) { [action] }
126
+ url ||= "#{PATH_SEPARATOR}#{(controller + action).join(PATH_SEPARATOR)}"
127
+
128
+ CLI::URL.call(url)
124
129
  end
125
130
 
126
131
  def route_http(action, http)
@@ -14,14 +14,14 @@ module Hanami
14
14
  super(inflector, app, slice, nil)
15
15
  end
16
16
 
17
- def classified_controller_name
17
+ def camelized_controller_name
18
18
  controller.map do |token|
19
19
  inflector.camelize(token)
20
20
  end.join(NAMESPACE_SEPARATOR)
21
21
  end
22
22
 
23
- def classified_action_name
24
- inflector.classify(action)
23
+ def camelized_action_name
24
+ inflector.camelize(action)
25
25
  end
26
26
 
27
27
  def underscored_controller_name
@@ -1,7 +1,7 @@
1
1
  # auto_register: false
2
2
  # frozen_string_literal: true
3
3
 
4
- module <%= classified_slice_name %>
5
- class Action < <%= classified_app_name %>::Action
4
+ module <%= camelized_slice_name %>
5
+ class Action < <%= camelized_app_name %>::Action
6
6
  end
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # auto_register: false
2
2
  # frozen_string_literal: true
3
3
 
4
- module <%= classified_slice_name %>
4
+ module <%= camelized_slice_name %>
5
5
  module Entities
6
6
  end
7
7
  end
@@ -3,8 +3,8 @@
3
3
  require "<%= underscored_app_name %>/repository"
4
4
  require_relative "entities"
5
5
 
6
- module <%= classified_slice_name %>
7
- class Repository < <%= classified_app_name %>::Repository
6
+ module <%= camelized_slice_name %>
7
+ class Repository < <%= camelized_app_name %>::Repository
8
8
  struct_namespace Entities
9
9
  end
10
10
  end
@@ -1,3 +1,3 @@
1
1
 
2
- slice :<%= underscored_slice_name %>, at: "<%= slice_url_prefix %>" do
2
+ slice :<%= underscored_slice_name %>, at: "<%= url %>" do
3
3
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= classified_slice_name %>
3
+ module <%= camelized_slice_name %>
4
4
  class Slice < Hanami::Slice
5
5
  end
6
6
  end
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "<%= underscored_app_name %>/view"
5
5
 
6
- module <%= classified_slice_name %>
7
- class View < <%= classified_app_name %>::View
6
+ module <%= camelized_slice_name %>
7
+ class View < <%= camelized_app_name %>::View
8
8
  end
9
9
  end
@@ -14,25 +14,25 @@ module Hanami
14
14
  @inflector = inflector
15
15
  end
16
16
 
17
- def call(app, slice, slice_url_prefix, context: SliceContext.new(inflector, app, slice, slice_url_prefix))
18
- fs.inject_line_at_block_bottom(fs.join("config", "routes.rb"), /define/, t("routes.erb", context).chomp)
17
+ def call(app, slice, url, context: SliceContext.new(inflector, app, slice, url))
18
+ fs.inject_line_at_class_bottom(
19
+ fs.join("config", "routes.rb"), "class Routes", t("routes.erb", context).chomp
20
+ )
19
21
 
20
22
  fs.mkdir(directory = "slices/#{slice}")
21
23
 
22
- fs.chdir(directory) do
23
- # fs.write("config/slice.rb", t("slice.erb", context))
24
- fs.write("action.rb", t("action.erb", context))
25
- # fs.write("view.rb", t("view.erb", context))
26
- # fs.write("entities.rb", t("entities.erb", context))
27
- # fs.write("repository.rb", t("repository.erb", context))
28
-
29
- fs.write("actions/.keep", t("keep.erb", context))
30
- # fs.write("views/.keep", t("keep.erb", context))
31
- # fs.write("templates/.keep", t("keep.erb", context))
32
- # fs.write("templates/layouts/.keep", t("keep.erb", context))
33
- # fs.write("entities/.keep", t("keep.erb", context))
34
- # fs.write("repositories/.keep", t("keep.erb", context))
35
- end
24
+ # fs.write("#{directory}/config/slice.rb", t("slice.erb", context))
25
+ fs.write(fs.join(directory, "action.rb"), t("action.erb", context))
26
+ # fs.write(fs.join(directory, "/view.rb"), t("view.erb", context))
27
+ # fs.write(fs.join(directory, "/entities.rb"), t("entities.erb", context))
28
+ # fs.write(fs.join(directory, "/repository.rb"), t("repository.erb", context))
29
+
30
+ fs.write(fs.join(directory, "actions/.keep"), t("keep.erb", context))
31
+ # fs.write(fs.join(directory, views/.keep"), t("keep.erb", context))
32
+ # fs.write(fs.join(directory, templates/.keep"), t("keep.erb", context))
33
+ # fs.write(fs.join(directory, templates/layouts/.keep"), t("keep.erb", context))
34
+ # fs.write(fs.join(directory, entities/.keep"), t("keep.erb", context))
35
+ # fs.write(fs.join(directory, repositories/.keep"), t("keep.erb", context))
36
36
  end
37
37
 
38
38
  private
@@ -7,14 +7,14 @@ module Hanami
7
7
  module Generators
8
8
  module App
9
9
  class SliceContext < Generators::Context
10
- def initialize(inflector, app, slice, slice_url_prefix)
10
+ def initialize(inflector, app, slice, url)
11
11
  @slice = slice
12
- @slice_url_prefix = slice_url_prefix
12
+ @url = url
13
13
  super(inflector, app)
14
14
  end
15
15
 
16
- def classified_slice_name
17
- inflector.classify(slice)
16
+ def camelized_slice_name
17
+ inflector.camelize(slice)
18
18
  end
19
19
 
20
20
  def underscored_slice_name
@@ -25,7 +25,7 @@ module Hanami
25
25
 
26
26
  attr_reader :slice
27
27
 
28
- attr_reader :slice_url_prefix
28
+ attr_reader :url
29
29
  end
30
30
  end
31
31
  end
@@ -19,8 +19,8 @@ module Hanami
19
19
  Version.gem_requirement
20
20
  end
21
21
 
22
- def classified_app_name
23
- inflector.classify(app)
22
+ def camelized_app_name
23
+ inflector.camelize(app)
24
24
  end
25
25
 
26
26
  def underscored_app_name
@@ -3,7 +3,7 @@
3
3
 
4
4
  require "hanami/action"
5
5
 
6
- module <%= classified_app_name %>
6
+ module <%= camelized_app_name %>
7
7
  class Action < Hanami::Action
8
8
  end
9
9
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "hanami"
4
4
 
5
- module <%= classified_app_name %>
5
+ module <%= camelized_app_name %>
6
6
  class App < Hanami::App
7
7
  end
8
8
  end
@@ -7,7 +7,7 @@ gem "hanami-router", "<%= hanami_version %>"
7
7
  gem "hanami-controller", "<%= hanami_version %>"
8
8
  gem "hanami-validations", "<%= hanami_version %>"
9
9
 
10
- gem "dry-types"
10
+ gem "dry-types", "~> 1.0", ">= 1.6.1"
11
11
  gem "puma"
12
12
  gem "rake"
13
13
 
@@ -15,6 +15,10 @@ group :development, :test do
15
15
  gem "dotenv"
16
16
  end
17
17
 
18
+ group :cli, :development do
19
+ gem "hanami-reloader"
20
+ end
21
+
18
22
  group :cli, :development, :test do
19
23
  gem "hanami-rspec"
20
24
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ max_threads_count = ENV.fetch("HANAMI_MAX_THREADS", 5)
4
+ min_threads_count = ENV.fetch("HANAMI_MIN_THREADS") { max_threads_count }
5
+ threads min_threads_count, max_threads_count
6
+
7
+ port ENV.fetch("HANAMI_PORT", 2300)
8
+ environment ENV.fetch("HANAMI_ENV", "development")
9
+ workers ENV.fetch("HANAMI_WEB_CONCURRENCY", 2)
10
+
11
+ on_worker_boot do
12
+ Hanami.shutdown
13
+ end
14
+
15
+ preload_app!
@@ -1 +1 @@
1
- # <%= classified_app_name %>
1
+ # <%= camelized_app_name %>
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module <%= classified_app_name %>
3
+ module <%= camelized_app_name %>
4
4
  class Routes < Hanami::Routes
5
- define do
6
- root { "Hello from Hanami" }
7
- end
5
+ root { "Hello from Hanami" }
8
6
  end
9
7
  end
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "<%= underscored_app_name %>/types"
4
-
5
- module <%= classified_app_name %>
3
+ module <%= camelized_app_name %>
6
4
  class Settings < Hanami::Settings
7
5
  # Define your app settings here, for example:
8
6
  #
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "dry/types"
4
4
 
5
- module <%= classified_app_name %>
5
+ module <%= camelized_app_name %>
6
6
  Types = Dry.Types
7
7
 
8
8
  module Types
@@ -4,7 +4,7 @@
4
4
  require "dry/validation"
5
5
  require "dry/schema/messages/i18n"
6
6
 
7
- module <%= classified_app_name %>
7
+ module <%= camelized_app_name %>
8
8
  module Validator < Dry::Validation::Contract
9
9
  config.messages.backend = :i18n
10
10
  config.messages.top_namespace = "validation"
@@ -40,6 +40,7 @@ module Hanami
40
40
  fs.write("config/app.rb", t("app.erb", context))
41
41
  fs.write("config/settings.rb", t("settings.erb", context))
42
42
  fs.write("config/routes.rb", t("routes.erb", context))
43
+ fs.write("config/puma.rb", t("puma.erb", context))
43
44
 
44
45
  fs.write("lib/tasks/.keep", t("keep.erb", context))
45
46
  fs.write("lib/#{app}/types.rb", t("types.erb", context))
@@ -11,9 +11,9 @@ module Hanami
11
11
  def inspect(include_arguments: false)
12
12
  max_path_length = @stack.map { |(path)| path.length }.max
13
13
 
14
- @stack.map { |path, middlewares|
15
- middlewares.map { |(middleware, arguments)|
16
- "#{path.ljust(max_path_length + 3)} #{format_middleware(middleware)}".tap { |line|
14
+ @stack.map { |path, middleware|
15
+ middleware.map { |(mware, arguments)|
16
+ "#{path.ljust(max_path_length + 3)} #{format_middleware(mware)}".tap { |line|
17
17
  line << " #{format_arguments(arguments)}" if include_arguments
18
18
  }
19
19
  }
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Hanami
4
4
  module CLI
5
- VERSION = "2.0.0.beta2"
5
+ VERSION = "2.0.0.beta4"
6
6
  end
7
7
  end
@@ -34,8 +34,9 @@ module Hanami
34
34
  hanami_app
35
35
  end
36
36
 
37
- define_method(:app) do
38
- hanami_app
37
+ define_method(:reload) do
38
+ puts "Reloading..."
39
+ Kernel.exec("#{$PROGRAM_NAME} console")
39
40
  end
40
41
 
41
42
  define_method(:method_missing) do |name, *args, &block|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.beta2
4
+ version: 2.0.0.beta4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-08-16 00:00:00.000000000 Z
11
+ date: 2022-10-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -58,20 +58,20 @@ dependencies:
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: '0.2'
61
+ version: '0.3'
62
62
  - - ">="
63
63
  - !ruby/object:Gem::Version
64
- version: 0.2.0
64
+ version: 0.3.0
65
65
  type: :runtime
66
66
  prerelease: false
67
67
  version_requirements: !ruby/object:Gem::Requirement
68
68
  requirements:
69
69
  - - "~>"
70
70
  - !ruby/object:Gem::Version
71
- version: '0.2'
71
+ version: '0.3'
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: 0.2.0
74
+ version: 0.3.0
75
75
  - !ruby/object:Gem::Dependency
76
76
  name: dry-inflector
77
77
  requirement: !ruby/object:Gem::Requirement
@@ -175,7 +175,7 @@ files:
175
175
  - lib/hanami/cli/commands/app/generate/action.rb
176
176
  - lib/hanami/cli/commands/app/generate/slice.rb
177
177
  - lib/hanami/cli/commands/app/install.rb
178
- - lib/hanami/cli/commands/app/middlewares.rb
178
+ - lib/hanami/cli/commands/app/middleware.rb
179
179
  - lib/hanami/cli/commands/app/routes.rb
180
180
  - lib/hanami/cli/commands/app/server.rb
181
181
  - lib/hanami/cli/commands/app/version.rb
@@ -188,6 +188,7 @@ files:
188
188
  - lib/hanami/cli/commands/gem/new.rb
189
189
  - lib/hanami/cli/commands/gem/version.rb
190
190
  - lib/hanami/cli/error.rb
191
+ - lib/hanami/cli/files.rb
191
192
  - lib/hanami/cli/generators/app/action.rb
192
193
  - lib/hanami/cli/generators/app/action/action.erb
193
194
  - lib/hanami/cli/generators/app/action/slice_action.erb
@@ -212,6 +213,7 @@ files:
212
213
  - lib/hanami/cli/generators/gem/app/env.erb
213
214
  - lib/hanami/cli/generators/gem/app/gemfile.erb
214
215
  - lib/hanami/cli/generators/gem/app/keep.erb
216
+ - lib/hanami/cli/generators/gem/app/puma.erb
215
217
  - lib/hanami/cli/generators/gem/app/rakefile.erb
216
218
  - lib/hanami/cli/generators/gem/app/readme.erb
217
219
  - lib/hanami/cli/generators/gem/app/routes.erb