hanami-cli 2.2.1 → 2.3.0.beta1
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 -0
- data/CHANGELOG.md +33 -0
- data/Gemfile +3 -1
- data/hanami-cli.gemspec +2 -0
- 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/server.rb +0 -1
- data/lib/hanami/cli/commands/app.rb +1 -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 +78 -101
- 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 +4 -5
- 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 +128 -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/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.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 +35 -41
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c770a27b290fd7bb7fcdd8db847ebd27723088374f8799b59d6e707e59404da0
|
4
|
+
data.tar.gz: 35aeda59b2833925c79d5ca3f604c410bb5512ec608c9f9527d7d8cbd1818c47
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e8a06cf2ca8224b1216c62d62356af0dc3ab4620b762fef531f0dec5b2c792816afe7f5a9a828170621a94f58370f04047ce0c86922e2730465417df9793169c
|
7
|
+
data.tar.gz: fb87833f3c11436dd3f313d9a3900d89395f6a8edef49a14a5e7b5c981081ec0bf859c11892d43502a005afd427bff7e8331faa733d1ec016875cef7fe59ad18
|
data/.github/workflows/ci.yml
CHANGED
@@ -24,9 +24,14 @@ jobs:
|
|
24
24
|
fail-fast: false
|
25
25
|
matrix:
|
26
26
|
ruby:
|
27
|
+
- "3.4"
|
27
28
|
- "3.3"
|
28
29
|
- "3.2"
|
29
30
|
- "3.1"
|
31
|
+
rack:
|
32
|
+
- "~> 2.0"
|
33
|
+
- "~> 3.0"
|
34
|
+
name: tests (Ruby ${{ matrix.ruby }}, Rack ${{ matrix.rack }})
|
30
35
|
env:
|
31
36
|
POSTGRES_BASE_URL: postgres://postgres:password@localhost:5432/hanami_cli_test
|
32
37
|
steps:
|
@@ -38,8 +43,12 @@ jobs:
|
|
38
43
|
with:
|
39
44
|
ruby-version: ${{matrix.ruby}}
|
40
45
|
bundler-cache: true
|
46
|
+
env:
|
47
|
+
RACK_VERSION_CONSTRAINT: ${{matrix.rack}}
|
41
48
|
- name: Run all tests
|
42
49
|
run: bundle exec rake spec
|
50
|
+
env:
|
51
|
+
RACK_VERSION_CONSTRAINT: ${{matrix.rack}}
|
43
52
|
services:
|
44
53
|
mysql:
|
45
54
|
image: mysql:latest
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,39 @@
|
|
2
2
|
|
3
3
|
Hanami Command Line Interface
|
4
4
|
|
5
|
+
## Unreleased
|
6
|
+
|
7
|
+
## v2.3.0.beta1 - 2025-10-03
|
8
|
+
|
9
|
+
### Added
|
10
|
+
|
11
|
+
- Running `hanami generate` commands within a slice directory will generate the file in that slice (@krzykamil in #298)
|
12
|
+
- Add `db rollback` command, supporting rolling back a single database at a time. (@krzykamil in #300)
|
13
|
+
- `console` command loads configured extensions from app config. Add these using e.g. `config.console.include MyModule, AnotherModule` (@alassek in #324)
|
14
|
+
- `console` command uses REPL engine configured in app config. Set this using e.g. `config.console.engine = :pry`; valid options are `:irb` (default) and `:pry` (@alassek in #324)
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
|
18
|
+
- Allow generated `public/400.html` and `public/500.html` to go into source control (@kyleplump in #290)
|
19
|
+
- Properly show database errors from failed `db drop` commands (@katafrakt in #281)
|
20
|
+
- Ensure consistent env var loading by disallowing Foreman's own env processing in generated `bin/dev` script (@cflipse in #305)
|
21
|
+
- Use the configured app inflector (and any custom inflections) for all commands (@timriley in #312)
|
22
|
+
- For app generated with `hanami new` with `--head`, include `hanami-cli` in the `Gemfile` (@afomera in #328)
|
23
|
+
|
24
|
+
### Changed
|
25
|
+
|
26
|
+
- Prevent `hanami new` from creating apps with confusing names (currently: "app" or "slice") (@seven1m in #272)
|
27
|
+
- Provide more helpful instructions in generated app README (@hanarimawi in #273)
|
28
|
+
- Prevent generators from overwriting files (@maxemitchell in #274, @stephannv in #319)
|
29
|
+
- Add irb as a gem dependency to avoid default gem warnings (@y-yagi in #294)
|
30
|
+
- Expand on comments `config/assets.js` and enable customization function by default (@robyurkowski in #293)
|
31
|
+
- Run `git init` at the end of `hanami new` (@krzykamil in #295)
|
32
|
+
- Prevent blank lines from showing when generating classes without deep module nesting (@cllns)
|
33
|
+
- Add `--skip-view` flag to `hanami new` (@kyleplump in #308)
|
34
|
+
- Support Rack 3 in addition to Rack 2 (for `hanami server` command) (@kyleplump in #289)
|
35
|
+
- Ensure compatibility with MySQL 9.4's CLI tools in `db structure load` command (@timriley in #315)
|
36
|
+
- Generated `Rakefile` will load tasks defined in `lib/tasks/` (@AlexanderZagaynov in #318)
|
37
|
+
|
5
38
|
## v2.2.1 - 2024-11-12
|
6
39
|
|
7
40
|
### Changed
|
data/Gemfile
CHANGED
@@ -18,7 +18,9 @@ gem "hanami-utils", github: "hanami/utils", branch: "main"
|
|
18
18
|
|
19
19
|
gem "dry-system", github: "dry-rb/dry-system", branch: "main"
|
20
20
|
|
21
|
-
|
21
|
+
if ENV["RACK_VERSION_CONSTRAINT"]
|
22
|
+
gem "rack", ENV["RACK_VERSION_CONSTRAINT"]
|
23
|
+
end
|
22
24
|
|
23
25
|
gem "mysql2"
|
24
26
|
gem "pg"
|
data/hanami-cli.gemspec
CHANGED
@@ -34,8 +34,10 @@ Gem::Specification.new do |spec|
|
|
34
34
|
spec.add_dependency "dry-cli", "~> 1.0", ">= 1.1.0"
|
35
35
|
spec.add_dependency "dry-files", "~> 1.0", ">= 1.0.2", "< 2"
|
36
36
|
spec.add_dependency "dry-inflector", "~> 1.0", "< 2"
|
37
|
+
spec.add_dependency "irb"
|
37
38
|
spec.add_dependency "rake", "~> 13.0"
|
38
39
|
spec.add_dependency "zeitwerk", "~> 2.6"
|
40
|
+
spec.add_dependency "rackup"
|
39
41
|
|
40
42
|
spec.add_development_dependency "rspec", "~> 3.9"
|
41
43
|
spec.add_development_dependency "rubocop", "~> 1.0"
|
data/lib/hanami/cli/command.rb
CHANGED
@@ -24,7 +24,6 @@ module Hanami
|
|
24
24
|
out: $stdout,
|
25
25
|
err: $stderr,
|
26
26
|
fs: Hanami::CLI::Files.new(out: out),
|
27
|
-
inflector: Dry::Inflector.new,
|
28
27
|
**opts
|
29
28
|
)
|
30
29
|
super
|
@@ -46,12 +45,15 @@ module Hanami
|
|
46
45
|
#
|
47
46
|
# @since 2.0.0
|
48
47
|
# @api public
|
49
|
-
def initialize(out:, err:, fs
|
48
|
+
def initialize(out:, err:, fs:)
|
50
49
|
super()
|
51
50
|
@out = out
|
52
51
|
@err = err
|
53
52
|
@fs = fs
|
54
|
-
|
53
|
+
end
|
54
|
+
|
55
|
+
def inflector
|
56
|
+
@inflector ||= Dry::Inflector.new
|
55
57
|
end
|
56
58
|
|
57
59
|
private
|
@@ -79,14 +81,6 @@ module Hanami
|
|
79
81
|
# @since 2.0.0
|
80
82
|
# @api public
|
81
83
|
attr_reader :fs
|
82
|
-
|
83
|
-
# Returns the inflector.
|
84
|
-
#
|
85
|
-
# @return [Dry::Inflector]
|
86
|
-
#
|
87
|
-
# @since 2.0.0
|
88
|
-
# @api public
|
89
|
-
attr_reader :inflector
|
90
84
|
end
|
91
85
|
end
|
92
86
|
end
|
@@ -14,10 +14,6 @@ module Hanami
|
|
14
14
|
# @since 2.0.0
|
15
15
|
# @api public
|
16
16
|
class Command < Hanami::CLI::Command
|
17
|
-
# @since 2.0.0
|
18
|
-
# @api private
|
19
|
-
ACTION_SEPARATOR = "." # TODO: rename to container key separator
|
20
|
-
|
21
17
|
# Overloads {Hanami::CLI::Commands::App::Command#call} to ensure an appropriate
|
22
18
|
# `HANAMI_ENV` environment variable is set.
|
23
19
|
#
|
@@ -44,6 +40,9 @@ module Hanami
|
|
44
40
|
Hanami::Env.load
|
45
41
|
|
46
42
|
super
|
43
|
+
rescue FileAlreadyExistsError => error
|
44
|
+
err.puts(error.message)
|
45
|
+
exit(1)
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
@@ -70,6 +69,8 @@ module Hanami
|
|
70
69
|
end
|
71
70
|
end
|
72
71
|
|
72
|
+
def inflector = app.inflector
|
73
|
+
|
73
74
|
# Runs another CLI command via its command class.
|
74
75
|
#
|
75
76
|
# @param klass [Hanami::CLI::Command]
|
@@ -80,7 +81,6 @@ module Hanami
|
|
80
81
|
def run_command(klass, ...)
|
81
82
|
klass.new(
|
82
83
|
out: out,
|
83
|
-
inflector: app.inflector,
|
84
84
|
fs: Hanami::CLI::Files,
|
85
85
|
).call(...)
|
86
86
|
end
|
@@ -11,7 +11,7 @@ module Hanami
|
|
11
11
|
|
12
12
|
option :gateway, required: false, desc: "Use database for gateway"
|
13
13
|
|
14
|
-
def call(app: false, slice: nil, gateway: nil, **)
|
14
|
+
def call(app: false, slice: nil, gateway: nil, command_exit: method(:exit), **)
|
15
15
|
exit_codes = []
|
16
16
|
|
17
17
|
databases(app: app, slice: slice, gateway: gateway).each do |database|
|
@@ -27,7 +27,7 @@ module Hanami
|
|
27
27
|
end
|
28
28
|
|
29
29
|
exit_codes.each do |code|
|
30
|
-
break
|
30
|
+
break command_exit.(code) if code > 0
|
31
31
|
end
|
32
32
|
|
33
33
|
re_run_development_command_in_test
|
@@ -0,0 +1,204 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative "../../app/command"
|
3
|
+
require_relative "structure/dump"
|
4
|
+
|
5
|
+
module Hanami
|
6
|
+
module CLI
|
7
|
+
module Commands
|
8
|
+
module App
|
9
|
+
module DB
|
10
|
+
class Rollback < DB::Command
|
11
|
+
desc "Rollback database to a previous migration"
|
12
|
+
|
13
|
+
argument :steps, desc: "Number of migrations to rollback", required: false
|
14
|
+
option :target, desc: "Target migration number", aliases: ["-t"]
|
15
|
+
option :dump, desc: "Dump structure after rolling back", default: true
|
16
|
+
option :gateway, required: false, desc: "Use database for gateway"
|
17
|
+
|
18
|
+
def call(steps: nil, app: false, slice: nil, gateway: nil, target: nil, dump: true, command_exit: method(:exit), **)
|
19
|
+
# We allow either a number of steps or a target migration number to be provided
|
20
|
+
# If steps is provided and target is not, we use steps as the target migration number, but we also have to
|
21
|
+
# make sure steps is a number, hence some additional logic around checking and converting to number
|
22
|
+
target = steps if steps && !target && !code_is_number?(steps)
|
23
|
+
steps_count = steps && code_is_number?(steps) ? Integer(steps) : 1
|
24
|
+
|
25
|
+
database = resolve_target_database(app: app, slice: slice, gateway: gateway, command_exit: command_exit)
|
26
|
+
return unless database
|
27
|
+
|
28
|
+
migration_code, migration_name = find_migration_target(target, steps_count, database)
|
29
|
+
|
30
|
+
if migration_name.nil?
|
31
|
+
output = if steps && code_is_number?(steps)
|
32
|
+
"==> migration file for #{steps} steps back was not found"
|
33
|
+
elsif target
|
34
|
+
"==> migration file for target #{target} was not found"
|
35
|
+
else
|
36
|
+
"==> no migrations to rollback"
|
37
|
+
end
|
38
|
+
|
39
|
+
out.puts output
|
40
|
+
return
|
41
|
+
end
|
42
|
+
|
43
|
+
measure "database #{database.name} rolled back to #{migration_name}" do
|
44
|
+
database.run_migrations(target: Integer(migration_code))
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
return unless dump && !re_running_in_test?
|
49
|
+
|
50
|
+
run_command(
|
51
|
+
Structure::Dump,
|
52
|
+
app: database.slice == self.app,
|
53
|
+
slice: database.slice == self.app ? nil : database.slice.slice_name.to_s,
|
54
|
+
gateway: database.gateway_name == :default ? nil : database.gateway_name.to_s,
|
55
|
+
command_exit: command_exit
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def resolve_target_database(app:, slice:, gateway:, command_exit:)
|
62
|
+
if gateway && !app && !slice
|
63
|
+
err.puts "When specifying --gateway, an --app or --slice must also be given"
|
64
|
+
command_exit.(1)
|
65
|
+
return
|
66
|
+
end
|
67
|
+
|
68
|
+
if slice
|
69
|
+
resolve_slice_database(slice, gateway, command_exit)
|
70
|
+
elsif app
|
71
|
+
resolve_app_database(gateway, command_exit)
|
72
|
+
else
|
73
|
+
resolve_default_database(command_exit)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def resolve_slice_database(slice_name, gateway, command_exit)
|
78
|
+
slice = resolve_slice(slice_name, command_exit)
|
79
|
+
return unless slice
|
80
|
+
|
81
|
+
databases = build_databases(slice)
|
82
|
+
|
83
|
+
if gateway
|
84
|
+
database = databases[gateway.to_sym]
|
85
|
+
unless database
|
86
|
+
err.puts %(No gateway "#{gateway}" found in slice "#{slice_name}")
|
87
|
+
command_exit.(1)
|
88
|
+
return
|
89
|
+
end
|
90
|
+
database
|
91
|
+
elsif databases.size == 1
|
92
|
+
databases.values.first
|
93
|
+
else
|
94
|
+
err.puts "Multiple gateways found in slice #{slice_name}. Please specify --gateway option."
|
95
|
+
command_exit.(1)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def resolve_app_database(gateway, command_exit)
|
100
|
+
databases = build_databases(app)
|
101
|
+
|
102
|
+
if gateway
|
103
|
+
database = databases[gateway.to_sym]
|
104
|
+
unless database
|
105
|
+
err.puts %(No gateway "#{gateway}" found in app)
|
106
|
+
command_exit.(1)
|
107
|
+
return
|
108
|
+
end
|
109
|
+
database
|
110
|
+
elsif databases.size == 1
|
111
|
+
databases.values.first
|
112
|
+
else
|
113
|
+
err.puts "Multiple gateways found in app. Please specify --gateway option."
|
114
|
+
command_exit.(1)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def resolve_default_database(command_exit)
|
119
|
+
all_dbs = all_databases
|
120
|
+
|
121
|
+
if all_dbs.empty?
|
122
|
+
err.puts "No databases found"
|
123
|
+
command_exit.(1)
|
124
|
+
elsif all_dbs.size == 1
|
125
|
+
all_dbs.first
|
126
|
+
else
|
127
|
+
app_databases = build_databases(app)
|
128
|
+
if app_databases.size == 1
|
129
|
+
app_databases.values.first
|
130
|
+
elsif app_databases.size > 1
|
131
|
+
err.puts "Multiple gateways found in app. Please specify --gateway option."
|
132
|
+
command_exit.(1)
|
133
|
+
return
|
134
|
+
else
|
135
|
+
err.puts "Multiple database contexts found. Please specify --app or --slice option."
|
136
|
+
command_exit.(1)
|
137
|
+
return
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def resolve_slice(slice_name, command_exit)
|
143
|
+
slice_name_sym = inflector.underscore(Shellwords.shellescape(slice_name)).to_sym
|
144
|
+
slice = app.slices[slice_name_sym]
|
145
|
+
|
146
|
+
unless slice
|
147
|
+
err.puts %(Slice "#{slice_name}" not found)
|
148
|
+
command_exit.(1)
|
149
|
+
return
|
150
|
+
end
|
151
|
+
|
152
|
+
ensure_database_slice(slice)
|
153
|
+
slice
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_migration_target(target, steps_count, database)
|
157
|
+
applied_migrations = database.applied_migrations
|
158
|
+
|
159
|
+
return if applied_migrations.empty?
|
160
|
+
|
161
|
+
if applied_migrations.one? && target.nil?
|
162
|
+
return initial_state(applied_migrations)
|
163
|
+
end
|
164
|
+
|
165
|
+
if target
|
166
|
+
migration = applied_migrations.detect { |m| m.split("_").first == target }
|
167
|
+
migration_code = migration&.split("_")&.first
|
168
|
+
migration_name = migration ? File.basename(migration, ".*") : nil
|
169
|
+
else
|
170
|
+
# When rolling back N steps, we want to target the migration that is N steps back
|
171
|
+
# If we have migrations [A, B, C, D] and want to rollback 2 steps from D,
|
172
|
+
# we want to target B (index -3, since we go back 2 steps + 1 for the target)
|
173
|
+
target_index = -(steps_count + 1)
|
174
|
+
|
175
|
+
if target_index.abs > applied_migrations.size
|
176
|
+
return initial_state(applied_migrations)
|
177
|
+
else
|
178
|
+
migration = applied_migrations[target_index]
|
179
|
+
migration_code = migration&.split("_")&.first
|
180
|
+
migration_name = migration ? File.basename(migration, ".*") : nil
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
[migration_code, migration_name]
|
185
|
+
end
|
186
|
+
|
187
|
+
def initial_state(applied_migrations)
|
188
|
+
migration = applied_migrations.first
|
189
|
+
|
190
|
+
migration_code = Integer(migration.split("_").first) - 1
|
191
|
+
migration_name = "initial state"
|
192
|
+
|
193
|
+
[migration_code, migration_name]
|
194
|
+
end
|
195
|
+
|
196
|
+
def code_is_number?(code)
|
197
|
+
code&.to_s&.match?(/^\d+$/) && !code.to_s.match?(/^\d{10,}$/)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
@@ -29,8 +29,9 @@ module Hanami
|
|
29
29
|
# @since 2.2.0
|
30
30
|
def exists?
|
31
31
|
result = exec_cli("mysql", %(-e "SHOW DATABASES LIKE '#{name}'" --batch))
|
32
|
+
raise Hanami::CLI::DatabaseExistenceCheckError.new(result.err) unless result.successful?
|
32
33
|
|
33
|
-
result.
|
34
|
+
result.out != ""
|
34
35
|
end
|
35
36
|
|
36
37
|
# @api private
|
@@ -47,7 +48,7 @@ module Hanami
|
|
47
48
|
def exec_load_command
|
48
49
|
exec_cli(
|
49
50
|
"mysql",
|
50
|
-
%(--execute "SET FOREIGN_KEY_CHECKS = 0; SOURCE #{structure_file}; SET FOREIGN_KEY_CHECKS = 1" --database #{escaped_name})
|
51
|
+
%(--commands --execute "SET FOREIGN_KEY_CHECKS = 0; SOURCE #{structure_file}; SET FOREIGN_KEY_CHECKS = 1" --database #{escaped_name})
|
51
52
|
)
|
52
53
|
end
|
53
54
|
|
@@ -33,7 +33,9 @@ module Hanami
|
|
33
33
|
# @since 2.2.0
|
34
34
|
def exists?
|
35
35
|
result = system_call.call("psql -t -A -c '\\list #{escaped_name}'", env: cli_env_vars)
|
36
|
-
result.
|
36
|
+
raise Hanami::CLI::DatabaseExistenceCheckError.new(result.err) unless result.successful?
|
37
|
+
|
38
|
+
result.out.include?("#{name}|") # start_with?
|
37
39
|
end
|
38
40
|
|
39
41
|
# @api private
|
@@ -13,7 +13,7 @@ module Hanami
|
|
13
13
|
module Generate
|
14
14
|
# @since 2.0.0
|
15
15
|
# @api private
|
16
|
-
class Action <
|
16
|
+
class Action < Command
|
17
17
|
# TODO: ideally the default format should lookup
|
18
18
|
# slice configuration (Action's `default_response_format`)
|
19
19
|
DEFAULT_FORMAT = "html"
|
@@ -29,29 +29,37 @@ module Hanami
|
|
29
29
|
private_constant :DEFAULT_SKIP_ROUTE
|
30
30
|
|
31
31
|
argument :name, required: true, desc: "Action name"
|
32
|
-
|
33
|
-
option :
|
34
|
-
|
32
|
+
|
33
|
+
option :url, as: :url_path, required: false, type: :string, desc: "Action URL path"
|
34
|
+
|
35
|
+
option :http, as: :http_method, required: false, type: :string, desc: "Action HTTP method"
|
36
|
+
|
35
37
|
option \
|
36
38
|
:skip_view,
|
37
39
|
required: false,
|
38
40
|
type: :flag,
|
39
41
|
default: DEFAULT_SKIP_VIEW,
|
40
42
|
desc: "Skip view and template generation"
|
43
|
+
|
44
|
+
# TODO: Implement this
|
41
45
|
option \
|
42
46
|
:skip_tests,
|
43
47
|
required: false,
|
44
48
|
type: :flag,
|
45
49
|
default: DEFAULT_SKIP_TESTS,
|
46
50
|
desc: "Skip test generation"
|
51
|
+
|
47
52
|
option \
|
48
53
|
:skip_route,
|
49
54
|
required: false,
|
50
55
|
type: :flag,
|
51
56
|
default: DEFAULT_SKIP_ROUTE,
|
52
57
|
desc: "Skip route generation"
|
58
|
+
|
53
59
|
option :slice, required: false, desc: "Slice name"
|
54
60
|
|
61
|
+
# option :format, required: false, type: :string, default: DEFAULT_FORMAT, desc: "Template format"
|
62
|
+
|
55
63
|
# rubocop:disable Layout/LineLength
|
56
64
|
example [
|
57
65
|
%(books.index # GET /books to: "books.index" (MyApp::Actions::Books::Index)),
|
@@ -68,52 +76,36 @@ module Hanami
|
|
68
76
|
]
|
69
77
|
# rubocop:enable Layout/LineLength
|
70
78
|
|
71
|
-
|
72
|
-
|
73
|
-
def initialize(
|
74
|
-
fs:, inflector:,
|
75
|
-
naming: Naming.new(inflector: inflector),
|
76
|
-
generator: Generators::App::Action.new(fs: fs, inflector: inflector),
|
77
|
-
**opts
|
78
|
-
)
|
79
|
-
super(fs: fs, inflector: inflector, **opts)
|
80
|
-
|
81
|
-
@naming = naming
|
82
|
-
@generator = generator
|
79
|
+
def generator_class
|
80
|
+
Generators::App::Action
|
83
81
|
end
|
84
82
|
|
85
|
-
# rubocop:disable Metrics/ParameterLists
|
86
|
-
|
87
83
|
# @since 2.0.0
|
88
84
|
# @api private
|
85
|
+
# rubocop:disable Lint/ParameterLists
|
89
86
|
def call(
|
90
87
|
name:,
|
91
|
-
|
92
|
-
|
93
|
-
|
88
|
+
slice: nil,
|
89
|
+
url_path: nil,
|
90
|
+
http_method: nil,
|
94
91
|
skip_view: DEFAULT_SKIP_VIEW,
|
95
|
-
skip_tests: DEFAULT_SKIP_TESTS, # rubocop:disable Lint/UnusedMethodArgument,
|
96
92
|
skip_route: DEFAULT_SKIP_ROUTE,
|
97
|
-
|
98
|
-
context: nil,
|
99
|
-
**
|
93
|
+
skip_tests: DEFAULT_SKIP_TESTS # rubocop:disable Lint/UnusedMethodArgument
|
100
94
|
)
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
95
|
+
name = Naming.new(inflector:).action_name(name)
|
96
|
+
|
97
|
+
raise InvalidActionNameError.new(name) unless name.include?(".")
|
98
|
+
|
99
|
+
super(
|
100
|
+
name:,
|
101
|
+
slice:,
|
102
|
+
url_path:,
|
103
|
+
skip_route:,
|
104
|
+
http_method:,
|
105
|
+
skip_view: skip_view || !Hanami.bundled?("hanami-view"),
|
106
|
+
)
|
110
107
|
end
|
111
|
-
|
112
|
-
# rubocop:enable Metrics/ParameterLists
|
113
|
-
|
114
|
-
private
|
115
|
-
|
116
|
-
attr_reader :naming, :generator
|
108
|
+
# rubocop:enable Lint/ParameterLists
|
117
109
|
end
|
118
110
|
end
|
119
111
|
end
|
@@ -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
|