hanami-cli 2.3.0.beta1 → 2.3.0.beta2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c770a27b290fd7bb7fcdd8db847ebd27723088374f8799b59d6e707e59404da0
4
- data.tar.gz: 35aeda59b2833925c79d5ca3f604c410bb5512ec608c9f9527d7d8cbd1818c47
3
+ metadata.gz: 4a260c1daad682ef7898890f677c8768f548512fdb591b2af60414ecaccc38fb
4
+ data.tar.gz: 58e9910a76e011243775ca794fc8dcd1769a1e31224136c5c8636a13a4e62f9d
5
5
  SHA512:
6
- metadata.gz: e8a06cf2ca8224b1216c62d62356af0dc3ab4620b762fef531f0dec5b2c792816afe7f5a9a828170621a94f58370f04047ce0c86922e2730465417df9793169c
7
- data.tar.gz: fb87833f3c11436dd3f313d9a3900d89395f6a8edef49a14a5e7b5c981081ec0bf859c11892d43502a005afd427bff7e8331faa733d1ec016875cef7fe59ad18
6
+ metadata.gz: 8f871a3391a7c985202dd517dc4775dfef0d4c7aa9c498e11d60ac235c12833b3de59ee0a5356ab48069753e95f166f3d41d1a3c3acb3b5b9acc5ec9c8d68376
7
+ data.tar.gz: 4649a2c721659508d58b5a90c1ec7d955d321d50277f254a2db187222539c96086bd5bdce91ab55f0a1ba5b5244e08a76ddd767be6aec2f3c223b532928bf225
@@ -27,7 +27,6 @@ jobs:
27
27
  - "3.4"
28
28
  - "3.3"
29
29
  - "3.2"
30
- - "3.1"
31
30
  rack:
32
31
  - "~> 2.0"
33
32
  - "~> 3.0"
data/CHANGELOG.md CHANGED
@@ -4,6 +4,23 @@ Hanami Command Line Interface
4
4
 
5
5
  ## Unreleased
6
6
 
7
+ ## v2.3.0.beta2 - 2025-10-17
8
+
9
+ ### Added
10
+
11
+ - Add `hanami run` command, to run your own code. For example, `hanami run path/to/script.rb` or `hanami run 'puts Hanami.app["repos.user_repo"].all.count'`. (@afomera in #338)
12
+
13
+ ### Changed
14
+
15
+ - Add `--skip-tests` flag to `generate action` command. (@kyleplump in #335)
16
+ - In new apps, updated generated types module to `Dry.Types(default: :strict)`. (@minaslater in #323)
17
+ - When generators add routes, add those routes to per-slice routes files (`config/routes.rb` within slice directories) if they exist. (@stephannv in #342)
18
+ - Drop support for Ruby 3.1
19
+
20
+ ### Fixed
21
+
22
+ - Handle mixed case names given to `generate` subcommands. (@cllns in #327)
23
+
7
24
  ## v2.3.0.beta1 - 2025-10-03
8
25
 
9
26
  ### Added
data/hanami-cli.gemspec CHANGED
@@ -28,7 +28,7 @@ Gem::Specification.new do |spec|
28
28
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
30
30
  spec.metadata["rubygems_mfa_required"] = "true"
31
- spec.required_ruby_version = ">= 3.1"
31
+ spec.required_ruby_version = ">= 3.2"
32
32
 
33
33
  spec.add_dependency "bundler", "~> 2.1"
34
34
  spec.add_dependency "dry-cli", "~> 1.0", ">= 1.1.0"
@@ -41,7 +41,6 @@ module Hanami
41
41
  default: DEFAULT_SKIP_VIEW,
42
42
  desc: "Skip view and template generation"
43
43
 
44
- # TODO: Implement this
45
44
  option \
46
45
  :skip_tests,
47
46
  required: false,
@@ -90,7 +89,7 @@ module Hanami
90
89
  http_method: nil,
91
90
  skip_view: DEFAULT_SKIP_VIEW,
92
91
  skip_route: DEFAULT_SKIP_ROUTE,
93
- skip_tests: DEFAULT_SKIP_TESTS # rubocop:disable Lint/UnusedMethodArgument
92
+ skip_tests: DEFAULT_SKIP_TESTS
94
93
  )
95
94
  name = Naming.new(inflector:).action_name(name)
96
95
 
@@ -103,6 +102,7 @@ module Hanami
103
102
  skip_route:,
104
103
  http_method:,
105
104
  skip_view: skip_view || !Hanami.bundled?("hanami-view"),
105
+ skip_tests:
106
106
  )
107
107
  end
108
108
  # rubocop:enable Lint/ParameterLists
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "hanami"
4
+ require_relative "../../errors"
5
+
6
+ module Hanami
7
+ module CLI
8
+ module Commands
9
+ module App
10
+ # Run a given code or file in the context of the application
11
+ #
12
+ # This command is useful for running scripts that need to load the application environment.
13
+ # You can pass a Ruby file to be executed, or you can run an interactive Ruby shell (IRB)
14
+ # with the application environment loaded.
15
+ #
16
+ # Examples:
17
+ #
18
+ # $ bundle exec hanami run path/to/script.rb
19
+ # $ bundle exec hanami run 'puts Hanami.app["repos.user_repo"].all.count'
20
+ #
21
+ # @since 2.0.0
22
+ # @api private
23
+ class Run < Hanami::CLI::Command
24
+ RunError = Class.new(StandardError)
25
+
26
+ desc "Run code in the context of the application"
27
+
28
+ example [
29
+ "path/to/script.rb # Run a Ruby script in the context of the application",
30
+ "'puts Hanami.app[\"repos.user_repo\"].all.count' # Run inline Ruby code in the context of the application",
31
+ ]
32
+
33
+ argument :code_or_path, required: true, desc: "Path to a Ruby file or inline Ruby code to be executed"
34
+
35
+ def initialize(command_exit: method(:exit), **opts)
36
+ super(**opts)
37
+ @command_exit = command_exit
38
+ end
39
+
40
+ # rubocop:disable Metrics/AbcSize
41
+ def call(code_or_path:, **)
42
+ require "hanami/prepare"
43
+
44
+ if File.exist?(code_or_path)
45
+ validate_file_path!(code_or_path)
46
+ Kernel.load code_or_path
47
+ else
48
+ validate_inline_code!(code_or_path)
49
+ begin
50
+ eval(code_or_path, binding, __FILE__, __LINE__) # rubocop:disable Security/Eval
51
+ rescue SyntaxError => e
52
+ err.puts "Syntax error in code: #{e.message}"
53
+ raise RunError, "Syntax error in code: #{e.message}"
54
+ rescue NameError => e
55
+ err.puts "Name error in code: #{e.message}"
56
+ raise RunError, "Name error in code: #{e.message}"
57
+ rescue StandardError => e
58
+ err.puts "Error executing code: #{e.class}: #{e.message}"
59
+ raise RunError, "Error executing code: #{e.class}: #{e.message}"
60
+ end
61
+ end
62
+ rescue RunError
63
+ @command_exit.call(1)
64
+ end
65
+ # rubocop:enable Metrics/AbcSize
66
+
67
+ private
68
+
69
+ def validate_file_path!(file_path)
70
+ errors = []
71
+
72
+ # Ensure the file is a Ruby file
73
+ unless file_path.end_with?(".rb")
74
+ errors << "Error: Only Ruby files (.rb) are allowed"
75
+ end
76
+
77
+ # Resolve the absolute path and ensure it's within the app directory
78
+ resolved_path = File.expand_path(file_path)
79
+ app_root = File.expand_path(Dir.pwd)
80
+
81
+ unless resolved_path.start_with?(app_root)
82
+ errors << "Error: File must be within the application directory"
83
+ end
84
+
85
+ # Check file size (prevent loading extremely large files)
86
+ file_size = File.size(file_path)
87
+ if file_size > 10 * 1024 * 1024 # 10MB limit
88
+ errors << "Error: File too large (maximum 10MB allowed)"
89
+ end
90
+
91
+ unless errors.empty?
92
+ errors.each { |error| err.puts error }
93
+ raise RunError, errors.join("\n")
94
+ end
95
+ end
96
+
97
+ def validate_inline_code!(code)
98
+ # Basic validation for inline code
99
+ if code.length > 10_000 # 10KB limit for inline code
100
+ err.puts "Error: Inline code too long (maximum 10,000 characters allowed)"
101
+ raise RunError, "Error: Inline code too long (maximum 10,000 characters allowed)"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -18,6 +18,7 @@ module Hanami
18
18
  register "console", Commands::App::Console, aliases: ["c"]
19
19
  register "server", Commands::App::Server, aliases: ["s"]
20
20
  register "routes", Commands::App::Routes
21
+ register "run", Commands::App::Run
21
22
  register "middleware", Commands::App::Middleware
22
23
 
23
24
  if Hanami.bundled?("hanami-assets")
@@ -47,7 +47,7 @@ module Hanami
47
47
  # @api public
48
48
  class FileAlreadyExistsError < Error
49
49
  ERROR_MESSAGE = <<~ERROR.chomp
50
- The file `%{file_path}` could not be generated because it already exists.
50
+ The file `%<file_path>s` could not be generated because it already exists.
51
51
  ERROR
52
52
 
53
53
  def initialize(file_path)
@@ -27,7 +27,7 @@ module Hanami
27
27
 
28
28
  # @since 2.0.0
29
29
  # @api private
30
- def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip_route:)
30
+ def call(key:, namespace:, base_path:, url_path:, http_method:, skip_view:, skip_route:, skip_tests:)
31
31
  insert_route(key:, namespace:, url_path:, http_method:) unless skip_route
32
32
 
33
33
  generate_action(key:, namespace:, base_path:, include_placeholder_body: skip_view)
@@ -83,8 +83,14 @@ module Hanami
83
83
  if namespace == Hanami.app.namespace
84
84
  fs.inject_line_at_class_bottom(routes_location, "class Routes", route)
85
85
  else
86
- slice_matcher = /slice[[:space:]]*:#{namespace}/
87
- fs.inject_line_at_block_bottom(routes_location, slice_matcher, route)
86
+ slice_routes = fs.join("slices", namespace, "config", "routes.rb")
87
+
88
+ if fs.exist?(slice_routes)
89
+ fs.inject_line_at_class_bottom(slice_routes, "class Routes", route)
90
+ else
91
+ slice_matcher = /slice[[:space:]]*:#{namespace}/
92
+ fs.inject_line_at_block_bottom(routes_location, slice_matcher, route)
93
+ end
88
94
  end
89
95
  end
90
96
 
@@ -95,7 +101,7 @@ module Hanami
95
101
  fs: fs,
96
102
  inflector: inflector,
97
103
  namespace: namespace,
98
- key: inflector.underscore(key),
104
+ key: key,
99
105
  base_path: base_path,
100
106
  parent_class_name: "#{inflector.camelize(namespace)}::Action",
101
107
  extra_namespace: "Actions",
@@ -24,7 +24,7 @@ module Hanami
24
24
  RubyClassFile.new(
25
25
  fs: fs,
26
26
  inflector: inflector,
27
- key: inflector.underscore(key),
27
+ key: key,
28
28
  namespace: namespace,
29
29
  base_path: base_path,
30
30
  ).create
@@ -20,7 +20,7 @@ module Hanami
20
20
  # @since 2.2.0
21
21
  # @api private
22
22
  def call(key:, namespace:, base_path:, gateway:)
23
- schema_name = key.split(KEY_SEPARATOR).last
23
+ schema_name = inflector.underscore(key.split(KEY_SEPARATOR).last)
24
24
  body_content = ["schema :#{schema_name}, infer: true"]
25
25
 
26
26
  body_content.prepend("gateway :#{gateway}") if gateway
@@ -17,11 +17,11 @@ module Hanami
17
17
  extra_namespace: nil,
18
18
  auto_register: nil,
19
19
  body: [],
20
- **opts
20
+ **_opts
21
21
  )
22
22
  @fs = fs
23
23
  @inflector = inflector
24
- @key = key
24
+ @key_segments = key.split(KEY_SEPARATOR).map { |segment| inflector.underscore(segment) }
25
25
  @namespace = namespace
26
26
  @base_path = base_path
27
27
  @extra_namespace = extra_namespace&.downcase
@@ -42,13 +42,13 @@ module Hanami
42
42
  # @api private
43
43
  def fully_qualified_name
44
44
  inflector.camelize(
45
- [namespace, extra_namespace, *key_parts].join("/"),
45
+ [namespace, extra_namespace, *key_segments].join("/"),
46
46
  )
47
47
  end
48
48
 
49
49
  # @api private
50
50
  def path
51
- fs.join(directory, "#{key_parts.last}.rb")
51
+ fs.join(directory, "#{key_segments.last}.rb")
52
52
  end
53
53
 
54
54
  private
@@ -57,7 +57,7 @@ module Hanami
57
57
  attr_reader(
58
58
  :fs,
59
59
  :inflector,
60
- :key,
60
+ :key_segments,
61
61
  :base_path,
62
62
  :namespace,
63
63
  :extra_namespace,
@@ -79,7 +79,7 @@ module Hanami
79
79
 
80
80
  # @api private
81
81
  def local_namespaces
82
- Array(extra_namespace) + key_parts[..-2]
82
+ Array(extra_namespace) + key_segments[..-2]
83
83
  end
84
84
 
85
85
  # @api private
@@ -100,7 +100,7 @@ module Hanami
100
100
 
101
101
  # @api private
102
102
  def constant_name
103
- normalize(key_parts.last)
103
+ normalize(key_segments.last)
104
104
  end
105
105
 
106
106
  # @api private
@@ -116,11 +116,6 @@ module Hanami
116
116
  def normalize(name)
117
117
  inflector.camelize(name).gsub(/[^\p{Alnum}]/, "")
118
118
  end
119
-
120
- # @api private
121
- def key_parts
122
- key.split(KEY_SEPARATOR)
123
- end
124
119
  end
125
120
  end
126
121
  end
@@ -44,7 +44,7 @@ module Hanami
44
44
  fs: fs,
45
45
  inflector: inflector,
46
46
  namespace: namespace,
47
- key: inflector.underscore(key),
47
+ key: key,
48
48
  base_path: base_path,
49
49
  parent_class_name: "#{inflector.camelize(namespace)}::View",
50
50
  extra_namespace: "Views",
@@ -22,7 +22,7 @@ module Hanami
22
22
 
23
23
  # @since 2.2.0
24
24
  # @api private
25
- KEY_SEPARATOR = %r{[.\/]}
25
+ KEY_SEPARATOR = %r{::|[.\/]}
26
26
  private_constant :KEY_SEPARATOR
27
27
 
28
28
  # @since 2.2.0
@@ -3,7 +3,7 @@
3
3
  require "dry/types"
4
4
 
5
5
  module <%= camelized_app_name %>
6
- Types = Dry.Types
6
+ Types = Dry.Types(default: :strict)
7
7
 
8
8
  module Types
9
9
  # Define your custom types here
@@ -6,6 +6,6 @@ module Hanami
6
6
  #
7
7
  # @api public
8
8
  # @since 2.0.0
9
- VERSION = "2.3.0.beta1"
9
+ VERSION = "2.3.0.beta2"
10
10
  end
11
11
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hanami-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.3.0.beta1
4
+ version: 2.3.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
@@ -255,6 +255,7 @@ files:
255
255
  - lib/hanami/cli/commands/app/install.rb
256
256
  - lib/hanami/cli/commands/app/middleware.rb
257
257
  - lib/hanami/cli/commands/app/routes.rb
258
+ - lib/hanami/cli/commands/app/run.rb
258
259
  - lib/hanami/cli/commands/app/server.rb
259
260
  - lib/hanami/cli/commands/app/version.rb
260
261
  - lib/hanami/cli/commands/gem.rb
@@ -342,7 +343,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
342
343
  requirements:
343
344
  - - ">="
344
345
  - !ruby/object:Gem::Version
345
- version: '3.1'
346
+ version: '3.2'
346
347
  required_rubygems_version: !ruby/object:Gem::Requirement
347
348
  requirements:
348
349
  - - ">="