hanami-cli 2.2.1 → 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 +4 -4
- data/.github/workflows/ci.yml +9 -1
- data/CHANGELOG.md +50 -0
- data/Gemfile +3 -1
- data/hanami-cli.gemspec +3 -1
- data/lib/hanami/cli/command.rb +5 -11
- data/lib/hanami/cli/commands/app/command.rb +5 -5
- data/lib/hanami/cli/commands/app/console.rb +1 -0
- data/lib/hanami/cli/commands/app/db/command.rb +0 -1
- data/lib/hanami/cli/commands/app/db/drop.rb +2 -2
- data/lib/hanami/cli/commands/app/db/rollback.rb +204 -0
- data/lib/hanami/cli/commands/app/db/utils/mysql.rb +3 -2
- data/lib/hanami/cli/commands/app/db/utils/postgres.rb +3 -1
- data/lib/hanami/cli/commands/app/generate/action.rb +32 -40
- data/lib/hanami/cli/commands/app/generate/command.rb +54 -17
- data/lib/hanami/cli/commands/app/generate/component.rb +4 -19
- data/lib/hanami/cli/commands/app/generate/part.rb +4 -21
- data/lib/hanami/cli/commands/app/generate/slice.rb +2 -2
- data/lib/hanami/cli/commands/app/generate/view.rb +5 -24
- data/lib/hanami/cli/commands/app/run.rb +108 -0
- data/lib/hanami/cli/commands/app/server.rb +0 -1
- data/lib/hanami/cli/commands/app.rb +2 -0
- data/lib/hanami/cli/commands/gem/new.rb +34 -3
- data/lib/hanami/cli/errors.rb +26 -0
- data/lib/hanami/cli/files.rb +20 -6
- data/lib/hanami/cli/generators/app/action.rb +83 -100
- data/lib/hanami/cli/generators/app/component.rb +11 -33
- data/lib/hanami/cli/generators/app/migration.rb +1 -1
- data/lib/hanami/cli/generators/app/operation.rb +4 -5
- data/lib/hanami/cli/generators/app/part.rb +42 -65
- data/lib/hanami/cli/generators/app/relation.rb +5 -6
- data/lib/hanami/cli/generators/app/repo.rb +3 -5
- data/lib/hanami/cli/generators/app/ruby_class_file.rb +32 -0
- data/lib/hanami/cli/generators/app/ruby_file.rb +123 -0
- data/lib/hanami/cli/generators/app/ruby_module_file.rb +28 -0
- data/lib/hanami/cli/generators/app/slice.rb +130 -37
- data/lib/hanami/cli/generators/app/struct.rb +3 -4
- data/lib/hanami/cli/generators/app/view.rb +40 -45
- data/lib/hanami/cli/generators/constants.rb +1 -1
- data/lib/hanami/cli/generators/context.rb +6 -0
- data/lib/hanami/cli/generators/gem/app/assets.js +14 -13
- data/lib/hanami/cli/generators/gem/app/dev +1 -1
- data/lib/hanami/cli/generators/gem/app/gemfile.erb +5 -0
- data/lib/hanami/cli/generators/gem/app/gitignore.erb +3 -1
- data/lib/hanami/cli/generators/gem/app/rakefile.erb +3 -0
- data/lib/hanami/cli/generators/gem/app/readme.erb +14 -0
- data/lib/hanami/cli/generators/gem/app/types.erb +1 -1
- data/lib/hanami/cli/generators/gem/app.rb +40 -37
- data/lib/hanami/cli/ruby_file_generator.rb +17 -8
- data/lib/hanami/cli/server.rb +15 -1
- data/lib/hanami/cli/version.rb +1 -1
- data/lib/hanami/console/context.rb +5 -0
- metadata +37 -42
- data/lib/hanami/cli/generators/app/action/action.erb +0 -17
- data/lib/hanami/cli/generators/app/action/slice_action.erb +0 -17
- data/lib/hanami/cli/generators/app/action/slice_template.html.erb +0 -1
- data/lib/hanami/cli/generators/app/action/slice_view.erb +0 -10
- data/lib/hanami/cli/generators/app/action/template.erb +0 -0
- data/lib/hanami/cli/generators/app/action/template.html.erb +0 -1
- data/lib/hanami/cli/generators/app/action/view.erb +0 -10
- data/lib/hanami/cli/generators/app/action_context.rb +0 -90
- data/lib/hanami/cli/generators/app/component/component.erb +0 -8
- data/lib/hanami/cli/generators/app/component/slice_component.erb +0 -8
- data/lib/hanami/cli/generators/app/component_context.rb +0 -82
- data/lib/hanami/cli/generators/app/part/app_base_part.erb +0 -9
- data/lib/hanami/cli/generators/app/part/app_part.erb +0 -13
- data/lib/hanami/cli/generators/app/part/slice_base_part.erb +0 -9
- data/lib/hanami/cli/generators/app/part/slice_part.erb +0 -13
- data/lib/hanami/cli/generators/app/part_context.rb +0 -82
- data/lib/hanami/cli/generators/app/ruby_file_writer.rb +0 -151
- data/lib/hanami/cli/generators/app/slice/action.erb +0 -7
- data/lib/hanami/cli/generators/app/slice/app_css.erb +0 -5
- data/lib/hanami/cli/generators/app/slice/app_js.erb +0 -1
- data/lib/hanami/cli/generators/app/slice/app_layout.erb +0 -18
- data/lib/hanami/cli/generators/app/slice/helpers.erb +0 -10
- data/lib/hanami/cli/generators/app/slice/keep.erb +0 -0
- data/lib/hanami/cli/generators/app/slice/operation.erb +0 -7
- data/lib/hanami/cli/generators/app/slice/relation.erb +0 -8
- data/lib/hanami/cli/generators/app/slice/repo.erb +0 -8
- data/lib/hanami/cli/generators/app/slice/routes.erb +0 -3
- data/lib/hanami/cli/generators/app/slice/struct.erb +0 -8
- data/lib/hanami/cli/generators/app/slice/view.erb +0 -7
- data/lib/hanami/cli/generators/app/slice_context.rb +0 -72
- data/lib/hanami/cli/generators/app/view/app_template.html.erb +0 -1
- data/lib/hanami/cli/generators/app/view/app_view.erb +0 -10
- data/lib/hanami/cli/generators/app/view/slice_template.html.erb +0 -1
- data/lib/hanami/cli/generators/app/view/slice_view.erb +0 -10
- data/lib/hanami/cli/generators/app/view_context.rb +0 -88
|
@@ -21,36 +21,73 @@ module Hanami
|
|
|
21
21
|
|
|
22
22
|
# @since 2.2.0
|
|
23
23
|
# @api private
|
|
24
|
-
def initialize(
|
|
25
|
-
fs:,
|
|
26
|
-
inflector:,
|
|
27
|
-
**opts
|
|
28
|
-
)
|
|
24
|
+
def initialize(fs:, out:, **)
|
|
29
25
|
super
|
|
30
|
-
@generator = generator_class.new(fs
|
|
26
|
+
@generator = generator_class.new(fs:, inflector:, out:)
|
|
31
27
|
end
|
|
32
28
|
|
|
29
|
+
# @since 2.2.0
|
|
30
|
+
# @api private
|
|
33
31
|
def generator_class
|
|
34
|
-
# Must be implemented by subclasses, with
|
|
35
|
-
# fs:,
|
|
32
|
+
# Must be implemented by subclasses, with initialize method that takes:
|
|
33
|
+
# fs:, out:
|
|
36
34
|
end
|
|
37
35
|
|
|
38
36
|
# @since 2.2.0
|
|
39
37
|
# @api private
|
|
40
|
-
def call(name:, slice: nil, **)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
namespace: slice,
|
|
45
|
-
base_path: fs.join("slices", inflector.underscore(slice))
|
|
46
|
-
)
|
|
47
|
-
else
|
|
38
|
+
def call(name:, slice: nil, **opts)
|
|
39
|
+
slice ||= detect_slice_from_cwd
|
|
40
|
+
|
|
41
|
+
if slice.nil?
|
|
48
42
|
generator.call(
|
|
49
43
|
key: name,
|
|
50
44
|
namespace: app.namespace,
|
|
51
|
-
base_path: "app"
|
|
45
|
+
base_path: "app",
|
|
46
|
+
**opts,
|
|
52
47
|
)
|
|
48
|
+
return
|
|
53
49
|
end
|
|
50
|
+
|
|
51
|
+
slice_root = slice.respond_to?(:root) ? slice.root : detect_slice_root(slice)
|
|
52
|
+
raise MissingSliceError.new(slice) unless fs.exist?(slice_root)
|
|
53
|
+
|
|
54
|
+
generator.call(
|
|
55
|
+
key: name,
|
|
56
|
+
namespace: slice,
|
|
57
|
+
base_path: slice_root,
|
|
58
|
+
**opts,
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def detect_slice_from_cwd
|
|
65
|
+
slices_by_root = app.slices.with_nested.each.to_h { |slice| [slice.root.to_s, slice] }
|
|
66
|
+
slices_by_root[fs.pwd]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Returns the root for the given slice name.
|
|
70
|
+
#
|
|
71
|
+
# This currently works with top-level slices only, and it simply appends the slice's
|
|
72
|
+
# name onto the "slices/" dir, returning e.g. "slices/main" when given "main".
|
|
73
|
+
#
|
|
74
|
+
# TODO: Make this work with nested slices when given slash-delimited slice names like
|
|
75
|
+
# "parent/child", which should look for "slices/parent/slices/child".
|
|
76
|
+
#
|
|
77
|
+
# This method makes two checks for the slice root (building off both `app.root` as well
|
|
78
|
+
# as `fs`). This is entirely to account for how we test commands, with most tests using
|
|
79
|
+
# an in-memory `fs` adapter, any files created via which will be invisible to the `app`,
|
|
80
|
+
# which doesn't know about the `fs`.
|
|
81
|
+
#
|
|
82
|
+
# FIXME: It would be better to find a way for this to make one check only. An ideal
|
|
83
|
+
# approach would be to use the slice_name to find actual slice registered within
|
|
84
|
+
# `app.slices`. To do this, we'd probably need to stop testing with an in-memory `fs`
|
|
85
|
+
# here.
|
|
86
|
+
def detect_slice_root(slice_name)
|
|
87
|
+
slice_root_in_fs = fs.join("slices", inflector.underscore(slice_name))
|
|
88
|
+
return slice_root_in_fs if fs.exist?(slice_root_in_fs)
|
|
89
|
+
|
|
90
|
+
app.root.join("slices", inflector.underscore(slice_name))
|
|
54
91
|
end
|
|
55
92
|
end
|
|
56
93
|
end
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "dry/inflector"
|
|
4
4
|
require "dry/files"
|
|
5
5
|
require "shellwords"
|
|
6
|
+
|
|
6
7
|
module Hanami
|
|
7
8
|
module CLI
|
|
8
9
|
module Commands
|
|
@@ -10,9 +11,8 @@ module Hanami
|
|
|
10
11
|
module Generate
|
|
11
12
|
# @api private
|
|
12
13
|
# @since 2.2.0
|
|
13
|
-
class Component <
|
|
14
|
+
class Component < Command
|
|
14
15
|
argument :name, required: true, desc: "Component name"
|
|
15
|
-
option :slice, required: false, desc: "Slice name"
|
|
16
16
|
|
|
17
17
|
example [
|
|
18
18
|
%(isbn_decoder (MyApp::IsbnDecoder)),
|
|
@@ -20,26 +20,11 @@ module Hanami
|
|
|
20
20
|
%(isbn_decoder --slice=admin (Admin::IsbnDecoder)),
|
|
21
21
|
%(Exporters::Complete::CSV (MyApp::Exporters::Complete::CSV)),
|
|
22
22
|
]
|
|
23
|
-
attr_reader :generator
|
|
24
|
-
private :generator
|
|
25
23
|
|
|
26
|
-
# @api private
|
|
27
24
|
# @since 2.2.0
|
|
28
|
-
def initialize(
|
|
29
|
-
fs:, inflector:,
|
|
30
|
-
generator: Generators::App::Component.new(fs: fs, inflector: inflector),
|
|
31
|
-
**opts
|
|
32
|
-
)
|
|
33
|
-
@generator = generator
|
|
34
|
-
super(fs: fs, inflector: inflector, **opts)
|
|
35
|
-
end
|
|
36
|
-
|
|
37
25
|
# @api private
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
|
|
41
|
-
|
|
42
|
-
generator.call(app.namespace, name, slice)
|
|
26
|
+
def generator_class
|
|
27
|
+
Generators::App::Component
|
|
43
28
|
end
|
|
44
29
|
end
|
|
45
30
|
end
|
|
@@ -11,12 +11,12 @@ module Hanami
|
|
|
11
11
|
module Generate
|
|
12
12
|
# @since 2.1.0
|
|
13
13
|
# @api private
|
|
14
|
-
class Part <
|
|
14
|
+
class Part < Command
|
|
15
15
|
DEFAULT_SKIP_TESTS = false
|
|
16
16
|
private_constant :DEFAULT_SKIP_TESTS
|
|
17
17
|
|
|
18
18
|
argument :name, required: true, desc: "Part name"
|
|
19
|
-
|
|
19
|
+
|
|
20
20
|
option \
|
|
21
21
|
:skip_tests,
|
|
22
22
|
required: false,
|
|
@@ -28,26 +28,9 @@ module Hanami
|
|
|
28
28
|
%(book (MyApp::Views::Parts::Book)),
|
|
29
29
|
%(book --slice=admin (Admin::Views::Parts::Book)),
|
|
30
30
|
]
|
|
31
|
-
attr_reader :generator
|
|
32
|
-
private :generator
|
|
33
|
-
|
|
34
|
-
# @since 2.0.0
|
|
35
|
-
# @api private
|
|
36
|
-
def initialize(
|
|
37
|
-
fs:, inflector:,
|
|
38
|
-
generator: Generators::App::Part.new(fs: fs, inflector: inflector),
|
|
39
|
-
**opts
|
|
40
|
-
)
|
|
41
|
-
super(fs: fs, inflector: inflector, **opts)
|
|
42
|
-
@generator = generator
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# @since 2.0.0
|
|
46
|
-
# @api private
|
|
47
|
-
def call(name:, slice: nil, skip_tests: DEFAULT_SKIP_TESTS, **) # rubocop:disable Lint/UnusedMethodArgument
|
|
48
|
-
slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
|
|
49
31
|
|
|
50
|
-
|
|
32
|
+
def generator_class
|
|
33
|
+
Generators::App::Part
|
|
51
34
|
end
|
|
52
35
|
end
|
|
53
36
|
end
|
|
@@ -49,11 +49,11 @@ module Hanami
|
|
|
49
49
|
# @since 2.0.0
|
|
50
50
|
# @api private
|
|
51
51
|
def initialize(
|
|
52
|
-
fs:,
|
|
52
|
+
fs:,
|
|
53
53
|
generator: Generators::App::Slice.new(fs: fs, inflector: inflector),
|
|
54
54
|
**opts
|
|
55
55
|
)
|
|
56
|
-
super(fs: fs,
|
|
56
|
+
super(fs: fs, **opts)
|
|
57
57
|
@generator = generator
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -13,40 +13,21 @@ module Hanami
|
|
|
13
13
|
module Generate
|
|
14
14
|
# @since 2.0.0
|
|
15
15
|
# @api private
|
|
16
|
-
class View <
|
|
17
|
-
# TODO: make
|
|
18
|
-
DEFAULT_FORMAT = "html"
|
|
19
|
-
private_constant :DEFAULT_FORMAT
|
|
20
|
-
|
|
16
|
+
class View < Command
|
|
17
|
+
# TODO: make format configurable
|
|
21
18
|
# TODO: make engine configurable
|
|
22
19
|
|
|
23
20
|
argument :name, required: true, desc: "View name"
|
|
24
|
-
option :slice, required: false, desc: "Slice name"
|
|
25
21
|
|
|
26
22
|
example [
|
|
27
23
|
%(books.index (MyApp::Actions::Books::Index)),
|
|
28
24
|
%(books.index --slice=admin (Admin::Actions::Books::Index)),
|
|
29
25
|
]
|
|
30
|
-
attr_reader :generator
|
|
31
|
-
private :generator
|
|
32
26
|
|
|
33
|
-
# @since 2.
|
|
27
|
+
# @since 2.2.0
|
|
34
28
|
# @api private
|
|
35
|
-
def
|
|
36
|
-
|
|
37
|
-
generator: Generators::App::View.new(fs: fs, inflector: inflector),
|
|
38
|
-
**opts
|
|
39
|
-
)
|
|
40
|
-
super(fs: fs, inflector: inflector, **opts)
|
|
41
|
-
@generator = generator
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
# @since 2.0.0
|
|
45
|
-
# @api private
|
|
46
|
-
def call(name:, format: DEFAULT_FORMAT, slice: nil, **)
|
|
47
|
-
slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
|
|
48
|
-
|
|
49
|
-
generator.call(app.namespace, name, format, slice)
|
|
29
|
+
def generator_class
|
|
30
|
+
Generators::App::View
|
|
50
31
|
end
|
|
51
32
|
end
|
|
52
33
|
end
|
|
@@ -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")
|
|
@@ -32,6 +33,7 @@ module Hanami
|
|
|
32
33
|
db.register "create", DB::Create
|
|
33
34
|
db.register "drop", DB::Drop
|
|
34
35
|
db.register "migrate", DB::Migrate
|
|
36
|
+
db.register "rollback", DB::Rollback
|
|
35
37
|
db.register "structure dump", DB::Structure::Dump
|
|
36
38
|
db.register "structure load", DB::Structure::Load
|
|
37
39
|
db.register "seed", DB::Seed
|
|
@@ -30,6 +30,11 @@ module Hanami
|
|
|
30
30
|
SKIP_DB_DEFAULT = false
|
|
31
31
|
private_constant :SKIP_DB_DEFAULT
|
|
32
32
|
|
|
33
|
+
# @since 2.2.0
|
|
34
|
+
# @api private
|
|
35
|
+
SKIP_VIEW_DEFAULT = false
|
|
36
|
+
private_constant :SKIP_VIEW_DEFAULT
|
|
37
|
+
|
|
33
38
|
# @since 2.2.0
|
|
34
39
|
# @api private
|
|
35
40
|
DATABASE_SQLITE = "sqlite"
|
|
@@ -46,6 +51,9 @@ module Hanami
|
|
|
46
51
|
# @api private
|
|
47
52
|
SUPPORTED_DATABASES = [DATABASE_SQLITE, DATABASE_POSTGRES, DATABASE_MYSQL].freeze
|
|
48
53
|
|
|
54
|
+
# @api private
|
|
55
|
+
FORBIDDEN_APP_NAMES = %w[app slice].freeze
|
|
56
|
+
|
|
49
57
|
desc "Generate a new Hanami app"
|
|
50
58
|
|
|
51
59
|
# @since 2.0.0
|
|
@@ -76,6 +84,12 @@ module Hanami
|
|
|
76
84
|
default: SKIP_DB_DEFAULT,
|
|
77
85
|
desc: "Skip including hanami-db"
|
|
78
86
|
|
|
87
|
+
# @since 2.2.0
|
|
88
|
+
# @api private
|
|
89
|
+
option :skip_view, type: :flag, required: false,
|
|
90
|
+
default: SKIP_VIEW_DEFAULT,
|
|
91
|
+
desc: "Skip including hanami-view"
|
|
92
|
+
|
|
79
93
|
# @since 2.2.0
|
|
80
94
|
# @api private
|
|
81
95
|
option :database, type: :string, required: false,
|
|
@@ -87,8 +101,9 @@ module Hanami
|
|
|
87
101
|
"bookshelf # Generate a new Hanami app in `bookshelf/' directory, using `Bookshelf' namespace",
|
|
88
102
|
"bookshelf --head # Generate a new Hanami app, using Hanami HEAD version from GitHub `main' branches",
|
|
89
103
|
"bookshelf --skip-install # Generate a new Hanami app, but it skips Hanami installation",
|
|
90
|
-
"bookshelf --skip-assets # Generate a new Hanami app without
|
|
104
|
+
"bookshelf --skip-assets # Generate a new Hanami app without hanami-assets",
|
|
91
105
|
"bookshelf --skip-db # Generate a new Hanami app without hanami-db",
|
|
106
|
+
"bookshelf --skip-view # Generate a new Hanami app without hanami-view",
|
|
92
107
|
"bookshelf --database={sqlite|postgres|mysql} # Generate a new Hanami app with a specified database (default: sqlite)",
|
|
93
108
|
]
|
|
94
109
|
# rubocop:enable Layout/LineLength
|
|
@@ -98,13 +113,13 @@ module Hanami
|
|
|
98
113
|
# @since 2.0.0
|
|
99
114
|
# @api private
|
|
100
115
|
def initialize(
|
|
101
|
-
fs:,
|
|
116
|
+
fs:,
|
|
102
117
|
bundler: CLI::Bundler.new(fs: fs),
|
|
103
118
|
generator: Generators::Gem::App.new(fs: fs, inflector: inflector),
|
|
104
119
|
system_call: SystemCall.new,
|
|
105
120
|
**opts
|
|
106
121
|
)
|
|
107
|
-
super(fs: fs,
|
|
122
|
+
super(fs: fs, **opts)
|
|
108
123
|
@bundler = bundler
|
|
109
124
|
@generator = generator
|
|
110
125
|
@system_call = system_call
|
|
@@ -120,12 +135,14 @@ module Hanami
|
|
|
120
135
|
skip_install: SKIP_INSTALL_DEFAULT,
|
|
121
136
|
skip_assets: SKIP_ASSETS_DEFAULT,
|
|
122
137
|
skip_db: SKIP_DB_DEFAULT,
|
|
138
|
+
skip_view: SKIP_VIEW_DEFAULT,
|
|
123
139
|
database: nil
|
|
124
140
|
)
|
|
125
141
|
# rubocop:enable Metrics/ParameterLists
|
|
126
142
|
app = inflector.underscore(app)
|
|
127
143
|
|
|
128
144
|
raise PathAlreadyExistsError.new(app) if fs.exist?(app)
|
|
145
|
+
raise ForbiddenAppNameError.new(app) if FORBIDDEN_APP_NAMES.include?(app)
|
|
129
146
|
|
|
130
147
|
normalized_database ||= normalize_database(database)
|
|
131
148
|
|
|
@@ -137,6 +154,7 @@ module Hanami
|
|
|
137
154
|
head: head,
|
|
138
155
|
skip_assets: skip_assets,
|
|
139
156
|
skip_db: skip_db,
|
|
157
|
+
skip_view: skip_view,
|
|
140
158
|
database: normalized_database
|
|
141
159
|
)
|
|
142
160
|
generator.call(app, context: context) do
|
|
@@ -158,6 +176,9 @@ module Hanami
|
|
|
158
176
|
|
|
159
177
|
out.puts "Running hanami install..."
|
|
160
178
|
run_install_command!(head: head)
|
|
179
|
+
|
|
180
|
+
out.puts "Initializing git repository..."
|
|
181
|
+
init_git_repository
|
|
161
182
|
end
|
|
162
183
|
end
|
|
163
184
|
end
|
|
@@ -193,6 +214,16 @@ module Hanami
|
|
|
193
214
|
end
|
|
194
215
|
end
|
|
195
216
|
end
|
|
217
|
+
|
|
218
|
+
# @api private
|
|
219
|
+
def init_git_repository
|
|
220
|
+
system_call.call("git", ["init"]).tap do |result|
|
|
221
|
+
unless result.successful?
|
|
222
|
+
out.puts "WARNING: Failed to initialize git repository"
|
|
223
|
+
out.puts(result.err.lines.map { |line| line.prepend(" ") })
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
196
227
|
end
|
|
197
228
|
end
|
|
198
229
|
end
|
data/lib/hanami/cli/errors.rb
CHANGED
|
@@ -44,6 +44,24 @@ module Hanami
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
# @api public
|
|
48
|
+
class FileAlreadyExistsError < Error
|
|
49
|
+
ERROR_MESSAGE = <<~ERROR.chomp
|
|
50
|
+
The file `%<file_path>s` could not be generated because it already exists.
|
|
51
|
+
ERROR
|
|
52
|
+
|
|
53
|
+
def initialize(file_path)
|
|
54
|
+
super(ERROR_MESSAGE % {file_path:})
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# @api public
|
|
59
|
+
class ForbiddenAppNameError < Error
|
|
60
|
+
def initialize(name)
|
|
61
|
+
super("Cannot create new Hanami app with the name: `#{name}'")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
47
65
|
# @since 2.0.0
|
|
48
66
|
# @api public
|
|
49
67
|
class MissingSliceError < Error
|
|
@@ -100,6 +118,14 @@ module Hanami
|
|
|
100
118
|
end
|
|
101
119
|
end
|
|
102
120
|
|
|
121
|
+
# @since 2.2.0
|
|
122
|
+
# @api public
|
|
123
|
+
class DatabaseExistenceCheckError < Error
|
|
124
|
+
def initialize(original_message)
|
|
125
|
+
super("Could not check if the database exists. Error message:\n#{original_message}")
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
103
129
|
# @since 2.2.0
|
|
104
130
|
# @api public
|
|
105
131
|
class ConflictingOptionsError < Error
|
data/lib/hanami/cli/files.rb
CHANGED
|
@@ -14,6 +14,13 @@ module Hanami
|
|
|
14
14
|
@out = out
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
# @api private
|
|
18
|
+
def create(path, *content)
|
|
19
|
+
raise FileAlreadyExistsError.new(path) if exist?(path)
|
|
20
|
+
|
|
21
|
+
write(path, *content)
|
|
22
|
+
end
|
|
23
|
+
|
|
17
24
|
# @since 2.0.0
|
|
18
25
|
# @api private
|
|
19
26
|
def write(path, *content)
|
|
@@ -33,10 +40,10 @@ module Hanami
|
|
|
33
40
|
# @since 2.0.0
|
|
34
41
|
# @api private
|
|
35
42
|
def mkdir(path)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
return if exist?(path)
|
|
44
|
+
|
|
45
|
+
super
|
|
46
|
+
created(dir_path(path))
|
|
40
47
|
end
|
|
41
48
|
|
|
42
49
|
# @since 2.0.0
|
|
@@ -46,6 +53,13 @@ module Hanami
|
|
|
46
53
|
super
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
def touch(path)
|
|
57
|
+
return if exist?(path)
|
|
58
|
+
|
|
59
|
+
super
|
|
60
|
+
created(path)
|
|
61
|
+
end
|
|
62
|
+
|
|
49
63
|
private
|
|
50
64
|
|
|
51
65
|
attr_reader :out
|
|
@@ -77,10 +91,10 @@ module Hanami
|
|
|
77
91
|
end
|
|
78
92
|
|
|
79
93
|
def within_folder(path)
|
|
80
|
-
out.puts "-> Within #{
|
|
94
|
+
out.puts "-> Within #{dir_path(path)}"
|
|
81
95
|
end
|
|
82
96
|
|
|
83
|
-
def
|
|
97
|
+
def dir_path(path)
|
|
84
98
|
path + ::File::SEPARATOR
|
|
85
99
|
end
|
|
86
100
|
end
|