hanami-cli 2.0.0.beta2 → 2.0.0.beta4

Sign up to get free protection for your applications and to get access to all the features.
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