brut 0.18.2 → 0.19.0
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/lib/brut/cli/apps/build_assets.rb +63 -32
- data/lib/brut/cli/apps/db.rb +198 -78
- data/lib/brut/cli/apps/deploy.rb +215 -140
- data/lib/brut/cli/apps/new/app.rb +127 -41
- data/lib/brut/cli/apps/scaffold.rb +108 -105
- data/lib/brut/cli/apps/test.rb +45 -26
- data/lib/brut/cli/commands/base_command.rb +58 -22
- data/lib/brut/cli/commands/compound_command.rb +2 -4
- data/lib/brut/cli/commands/execution_context.rb +17 -10
- data/lib/brut/cli/commands/help.rb +112 -6
- data/lib/brut/cli/commands/output_error.rb +1 -1
- data/lib/brut/cli/execute_result.rb +4 -0
- data/lib/brut/cli/executor.rb +7 -7
- data/lib/brut/cli/logger.rb +122 -0
- data/lib/brut/cli/output.rb +9 -45
- data/lib/brut/cli/parsed_command_line.rb +33 -10
- data/lib/brut/cli/runner.rb +37 -8
- data/lib/brut/cli/terminal.rb +74 -0
- data/lib/brut/cli/terminal_theme.rb +131 -0
- data/lib/brut/cli.rb +7 -3
- data/lib/brut/framework/mcp.rb +4 -3
- data/lib/brut/front_end/asset_metadata.rb +9 -5
- data/lib/brut/spec_support/cli_command_support.rb +9 -3
- data/lib/brut/spec_support/e2e_test_server.rb +3 -3
- data/lib/brut/tui/script.rb +1 -1
- data/lib/brut/version.rb +1 -1
- metadata +18 -4
- data/lib/brut/cli/apps/deploy_base.rb +0 -86
- data/lib/brut/cli/apps/heroku_container_based_deploy.rb +0 -226
- data/templates/segments/Heroku/bin/deploy +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85deaeeb3b8c032710ef05c4d61c0190e5c4f12294a5b38e1f154bb8c106dc96
|
|
4
|
+
data.tar.gz: d9530ce8adebb693f5b9334213b3d26f5c82078634279ae448073693afe751f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4db91cd807771f84cf32e6f12e9520863ed1bf5718b6ff8efe0e703126b8d3ee62f837bdd78782ae2523f6ca8bac97d3d190952b818d5c65d9234fcc020a76fc
|
|
7
|
+
data.tar.gz: 9b040971b7c3a8a037a77356f60e71f83a8c27b37537b50456c52b141bd2359a47cd93b2148b661ff995b85ad0313ae3440899e4fa35946881aa66d92fee6f76
|
|
@@ -6,22 +6,44 @@ require "brut/cli"
|
|
|
6
6
|
class Brut::CLI::Apps::BuildAssets < Brut::CLI::Commands::BaseCommand
|
|
7
7
|
def description = "Build and manage code and assets destined for the browser, such as CSS, JS, or images"
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
class BaseCommand < Brut::CLI::Commands::BaseCommand
|
|
10
|
+
def bootstrap? = false
|
|
11
|
+
def default_rack_env = "development"
|
|
12
|
+
def opts = [
|
|
13
|
+
[
|
|
14
|
+
"--[no-]clean",
|
|
15
|
+
"If set, any old files from previous runs are deleted. Defaults to false in production, true everywhere else.",
|
|
16
|
+
],
|
|
17
|
+
]
|
|
18
|
+
def friendly_name(file)
|
|
19
|
+
Pathname(file).relative_path_from(Brut.container.project_root).to_s
|
|
20
|
+
end
|
|
21
|
+
end
|
|
15
22
|
|
|
16
23
|
def name = "build-assets"
|
|
17
24
|
|
|
18
|
-
def
|
|
25
|
+
def default_command
|
|
26
|
+
@default_command ||= All.new
|
|
27
|
+
end
|
|
19
28
|
def bootstrap? = default_command.bootstrap?
|
|
29
|
+
def default_rack_env = default_command.default_rack_env
|
|
30
|
+
|
|
31
|
+
def run
|
|
32
|
+
delegate_to_command(default_command)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def commands = [
|
|
36
|
+
All.new,
|
|
37
|
+
Images.new,
|
|
38
|
+
Css.new,
|
|
39
|
+
Js.new,
|
|
40
|
+
]
|
|
41
|
+
|
|
20
42
|
|
|
21
43
|
class All < Brut::CLI::Commands::CompoundCommand
|
|
22
44
|
def default_rack_env = "development"
|
|
23
45
|
def description = "Build all assets"
|
|
24
|
-
def bootstrap? =
|
|
46
|
+
def bootstrap? = true
|
|
25
47
|
|
|
26
48
|
def initialize
|
|
27
49
|
super([
|
|
@@ -41,24 +63,31 @@ class Brut::CLI::Apps::BuildAssets < Brut::CLI::Commands::BaseCommand
|
|
|
41
63
|
end
|
|
42
64
|
end
|
|
43
65
|
|
|
44
|
-
class Images <
|
|
45
|
-
def default_rack_env = "development"
|
|
66
|
+
class Images < BaseCommand
|
|
46
67
|
def description = "Copy images to the public folder"
|
|
47
68
|
def detailed_description = %{
|
|
48
69
|
This is to ensure that any images your code references will end up in the public directory, so they are served properly. This is not for managing images that may be referenced in CSS files. See the `css` command for information on that.
|
|
49
70
|
}
|
|
50
|
-
def bootstrap? = false
|
|
51
71
|
|
|
52
72
|
def run
|
|
53
73
|
src_dir = Brut.container.images_src_dir
|
|
54
74
|
dest_dir = Brut.container.images_root_dir
|
|
55
75
|
|
|
56
|
-
|
|
76
|
+
puts "Syncing images from #{theme.code.render(friendly_name(src_dir.to_s))} to #{theme.code.render(friendly_name(dest_dir.to_s))}"
|
|
77
|
+
rsync_args = [
|
|
78
|
+
"--archive",
|
|
79
|
+
"--verbose",
|
|
80
|
+
]
|
|
81
|
+
if options.clean?(default: options.env != "production")
|
|
82
|
+
puts "Deleting old images from #{theme.code.render(friendly_name(dest_dir.to_s))}"
|
|
83
|
+
rsync_args << "--delete"
|
|
84
|
+
end
|
|
85
|
+
system! "rsync #{rsync_args.join(' ')} \"#{src_dir}/\" \"#{dest_dir}\""
|
|
57
86
|
end
|
|
58
87
|
end
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
|
|
89
|
+
|
|
90
|
+
class Css < BaseCommand
|
|
62
91
|
def description = "Builds a single CSS file suitable for sending to the browser"
|
|
63
92
|
|
|
64
93
|
def detailed_description = %{
|
|
@@ -66,7 +95,6 @@ This is to ensure that any images your code references will end up in the public
|
|
|
66
95
|
|
|
67
96
|
To ensure this happens correctly, your url() or other function must reference the file as a relative file from where your actual source CSS file is located. For example, a font named some-font.ttf would be in app/src/front_end/fonts and to reference this from app/src/front_end/css/index.css you'd use the url "../fonts/some-font.ttf"
|
|
68
97
|
}
|
|
69
|
-
def bootstrap? = false
|
|
70
98
|
|
|
71
99
|
def run
|
|
72
100
|
css_bundle = Brut.container.css_bundle_output_dir / "styles.css"
|
|
@@ -75,34 +103,37 @@ This is to ensure that any images your code references will end up in the public
|
|
|
75
103
|
asset_metadata_file = Brut.container.asset_metadata_file
|
|
76
104
|
|
|
77
105
|
if options.clean?(default: options.env != "production")
|
|
78
|
-
puts "Cleaning old CSS files from #{Brut.container.css_bundle_output_dir}"
|
|
106
|
+
puts "Cleaning old CSS files from #{theme.code.render(friendly_name(Brut.container.css_bundle_output_dir.to_s))}"
|
|
79
107
|
Dir[Brut.container.css_bundle_output_dir / "*.*"].each do |file|
|
|
80
108
|
if File.file?(file)
|
|
81
|
-
puts "Deleting #{file}"
|
|
109
|
+
puts theme.weak.render(" Deleting #{theme.code.render(friendly_name(file))}")
|
|
82
110
|
FileUtils.rm(file)
|
|
83
111
|
end
|
|
84
112
|
end
|
|
85
113
|
end
|
|
86
114
|
|
|
87
|
-
|
|
88
|
-
|
|
115
|
+
# NOTE: esbuild outputs its normal messages on stderr which is fucking stupid
|
|
116
|
+
command = "npx esbuild --loader:.ttf=copy --loader:.otf=copy --metafile=#{esbuild_metafile} --entry-names=[name]-[hash] --sourcemap --bundle #{css_bundle_source} --outfile=#{css_bundle} 2>&1"
|
|
117
|
+
puts "Building CSS bundle '#{theme.code.render(friendly_name(css_bundle))}'"
|
|
118
|
+
|
|
89
119
|
system!(command)
|
|
90
120
|
|
|
91
121
|
if !File.exist?(esbuild_metafile)
|
|
92
|
-
|
|
122
|
+
error "'#{esbuild_metafile}' was not generated"
|
|
123
|
+
puts theme.error.render("esbuild did not generate the metafile we asked for (#{esbuild_metafile})")
|
|
124
|
+
puts theme.error.render("This file is required to continue")
|
|
93
125
|
return 1
|
|
94
126
|
end
|
|
95
127
|
|
|
96
|
-
asset_metadata = Brut::FrontEnd::AssetMetadata.new(asset_metadata_file:,
|
|
128
|
+
asset_metadata = Brut::FrontEnd::AssetMetadata.new(asset_metadata_file:,logger:execution_context.logger)
|
|
97
129
|
asset_metadata.merge!(extension: ".css", esbuild_metafile:)
|
|
98
130
|
asset_metadata.save!
|
|
99
131
|
0
|
|
100
132
|
end
|
|
101
133
|
end
|
|
102
|
-
class Js <
|
|
103
|
-
def default_rack_env = "development"
|
|
134
|
+
class Js < BaseCommand
|
|
104
135
|
def description = "Builds and bundles JavaScript destined for the browser"
|
|
105
|
-
def opts = [
|
|
136
|
+
def opts = super + [
|
|
106
137
|
[
|
|
107
138
|
"--output-file=FILE",
|
|
108
139
|
"Bundle to create that will be sent to the browser, relative to the JS public folder. Default is app.js",
|
|
@@ -110,9 +141,8 @@ This is to ensure that any images your code references will end up in the public
|
|
|
110
141
|
[
|
|
111
142
|
"--source-file=FILE",
|
|
112
143
|
"Entry point used to create the bundle, relative to the source JS folder. Default is index.js",
|
|
113
|
-
]
|
|
144
|
+
],
|
|
114
145
|
]
|
|
115
|
-
def bootstrap? = false
|
|
116
146
|
|
|
117
147
|
def run
|
|
118
148
|
js_bundle = Brut.container.js_bundle_output_dir / options.output_file(default: "app.js")
|
|
@@ -122,25 +152,26 @@ This is to ensure that any images your code references will end up in the public
|
|
|
122
152
|
|
|
123
153
|
name_with_hash_regexp = /app\/public\/(?<path>.+)\/(?<name>.+)\-(?<hash>.+)\.js/
|
|
124
154
|
if options.clean?(default: options.env != "production")
|
|
125
|
-
puts "Cleaning old JS files from #{Brut.container.js_bundle_output_dir}"
|
|
155
|
+
puts "Cleaning old JS files from #{friendly_name(Brut.container.js_bundle_output_dir)}"
|
|
126
156
|
Dir[Brut.container.js_bundle_output_dir / "*.*"].each do |file|
|
|
127
157
|
if File.file?(file)
|
|
128
|
-
puts "Deleting #{file}"
|
|
158
|
+
puts theme.weak.render(" Deleting #{theme.code.render(friendly_name(file))}")
|
|
129
159
|
FileUtils.rm(file)
|
|
130
160
|
end
|
|
131
161
|
end
|
|
132
162
|
end
|
|
133
163
|
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
# NOTE: esbuild outputs its normal messages on stderr which is fucking stupid
|
|
165
|
+
command = "npx esbuild --metafile=#{esbuild_metafile} --entry-names=[name]-[hash] --sourcemap --bundle #{js_bundle_source} --outfile=#{js_bundle} 2>&1"
|
|
166
|
+
puts "Building JS bundle '#{theme.code.render(friendly_name(js_bundle))}'"
|
|
136
167
|
system!(command)
|
|
137
168
|
|
|
138
169
|
if !File.exist?(esbuild_metafile)
|
|
139
|
-
|
|
170
|
+
error "'#{esbuild_metafile}' was not generated - cannot continue"
|
|
140
171
|
return 1
|
|
141
172
|
end
|
|
142
173
|
|
|
143
|
-
asset_metadata = Brut::FrontEnd::AssetMetadata.new(asset_metadata_file:,
|
|
174
|
+
asset_metadata = Brut::FrontEnd::AssetMetadata.new(asset_metadata_file:,logger: execution_context.logger)
|
|
144
175
|
asset_metadata.merge!(extension: ".js", esbuild_metafile:)
|
|
145
176
|
asset_metadata.save!
|
|
146
177
|
0
|
data/lib/brut/cli/apps/db.rb
CHANGED
|
@@ -7,8 +7,6 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
7
7
|
def description = "Manage your database in development, test, and production"
|
|
8
8
|
def name = "db"
|
|
9
9
|
|
|
10
|
-
def default_command_class = Status
|
|
11
|
-
|
|
12
10
|
class Status < Brut::CLI::Commands::BaseCommand
|
|
13
11
|
def description = "Check the status of the database and migrations"
|
|
14
12
|
def default_rack_env = "development"
|
|
@@ -17,8 +15,6 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
17
15
|
def run
|
|
18
16
|
database_name = URI(Brut.container.database_url).path.gsub(/^\//,"")
|
|
19
17
|
connection = Brut.container.sequel_db_handle
|
|
20
|
-
stdout.puts "Database server is up"
|
|
21
|
-
stdout.puts "Database #{database_name} exists"
|
|
22
18
|
migrations_run = if connection.table_exists?("schema_migrations")
|
|
23
19
|
connection["select filename from schema_migrations order by filename"].all.map { |_| _[:filename] }
|
|
24
20
|
else
|
|
@@ -27,16 +23,7 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
27
23
|
migration_files = Dir[Brut.container.migrations_dir / "*.rb"].map { |file|
|
|
28
24
|
filename = Pathname(file).basename.to_s
|
|
29
25
|
}
|
|
30
|
-
|
|
31
|
-
stdout.puts("✅ NO MIGRATION FILES TO RUN")
|
|
32
|
-
else
|
|
33
|
-
max_length = migration_files.map(&:length).max
|
|
34
|
-
printf_string = "%-#{max_length}s - %s\n"
|
|
35
|
-
migration_files.each do |filename|
|
|
36
|
-
applied = migrations_run.include?(filename)
|
|
37
|
-
stdout.printf(printf_string,filename,applied ? "✅ APPLIED" : "❌ NOT APPLIED")
|
|
38
|
-
end
|
|
39
|
-
end
|
|
26
|
+
puts status_table(server_up: true, database_exists: true, database_name:, migrations_run:, migration_files:).render
|
|
40
27
|
0
|
|
41
28
|
rescue Sequel::DatabaseConnectionError => ex
|
|
42
29
|
uri_no_database = URI(Brut.container.database_url.to_s)
|
|
@@ -44,15 +31,64 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
44
31
|
uri_no_database.path = ""
|
|
45
32
|
begin
|
|
46
33
|
connection = Sequel.connect(uri_no_database.to_s)
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
puts status_table(server_up: true, database_exists: false, database_name:, migrations_run: [], migration_files: []).render
|
|
35
|
+
puts [
|
|
36
|
+
theme.warning.render("Try creating the database with"),
|
|
37
|
+
theme.code.render("brut db create"),
|
|
38
|
+
].join(" ")
|
|
49
39
|
0
|
|
50
40
|
rescue => ex2
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
puts status_table(server_up: false, database_exists: false, database_name:, migrations_run: [], migration_files: []).render
|
|
42
|
+
puts theme.error.render("Database server is not running at #{uri_no_database}: #{ex2.message}")
|
|
43
|
+
puts theme.error.render("This could be a problem with your dev environment generally, or your .env.test or .env.test.local files")
|
|
53
44
|
1
|
|
54
45
|
end
|
|
55
46
|
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def status_table(server_up:, database_exists:, database_name:, migrations_run:, migration_files:)
|
|
51
|
+
rows = [
|
|
52
|
+
[
|
|
53
|
+
"Database Server",
|
|
54
|
+
server_up ? theme.success.render("✅ UP") : theme.error.render("❌ DOWN")
|
|
55
|
+
],
|
|
56
|
+
]
|
|
57
|
+
if server_up
|
|
58
|
+
rows << [
|
|
59
|
+
"Database #{theme.code.render(database_name)}",
|
|
60
|
+
database_exists ? theme.success.render("✅ Exists") : theme.error.render("❌ DOES NOT EXIST")
|
|
61
|
+
]
|
|
62
|
+
end
|
|
63
|
+
if database_exists
|
|
64
|
+
if migration_files.empty? && migrations_run.empty?
|
|
65
|
+
rows << [
|
|
66
|
+
"Migrations",
|
|
67
|
+
"✅ NO MIGRATION FILES TO RUN"
|
|
68
|
+
]
|
|
69
|
+
else
|
|
70
|
+
migration_files.each do |filename|
|
|
71
|
+
applied = if migrations_run.include?(filename)
|
|
72
|
+
theme.success.render("✅ APPLIED")
|
|
73
|
+
else
|
|
74
|
+
theme.warning.render("❌ NOT APPLIED")
|
|
75
|
+
end
|
|
76
|
+
rows << [ filename, applied ]
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
Lipgloss::Table.new.
|
|
81
|
+
headers([ "Check", "Status" ]).
|
|
82
|
+
rows(rows).
|
|
83
|
+
style_func(rows: rows.length, columns: 2) { |row,column|
|
|
84
|
+
if row == Lipgloss::Table::HEADER_ROW
|
|
85
|
+
Lipgloss::Style.new.inherit(theme.header).padding_left(1).padding_right(1)
|
|
86
|
+
else
|
|
87
|
+
Lipgloss::Style.new.inherit(theme.none).padding_left(1).padding_right(1)
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
end
|
|
91
|
+
|
|
56
92
|
end
|
|
57
93
|
|
|
58
94
|
class Create < Brut::CLI::Commands::BaseCommand
|
|
@@ -61,24 +97,56 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
61
97
|
def bootstrap? = false
|
|
62
98
|
|
|
63
99
|
def run
|
|
64
|
-
connection = Sequel.connect(Brut.container.database_url)
|
|
65
|
-
stdout.puts "Database already exists"
|
|
66
|
-
connection.disconnect
|
|
67
|
-
0
|
|
68
|
-
rescue Sequel::DatabaseConnectionError => ex
|
|
69
100
|
uri_no_database = URI(Brut.container.database_url.to_s)
|
|
70
101
|
database_name = uri_no_database.path.gsub(/^\//,"")
|
|
71
102
|
uri_no_database.path = ""
|
|
72
103
|
begin
|
|
73
|
-
connection = Sequel.connect(
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
connection = Sequel.connect(Brut.container.database_url)
|
|
105
|
+
puts [
|
|
106
|
+
theme.success.render("✅ Database"),
|
|
107
|
+
theme.code.render(database_name),
|
|
108
|
+
theme.success.render("already exists"),
|
|
109
|
+
].join(" ")
|
|
76
110
|
connection.disconnect
|
|
77
111
|
0
|
|
78
|
-
rescue Sequel::DatabaseConnectionError =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
rescue Sequel::DatabaseConnectionError => ex
|
|
113
|
+
begin
|
|
114
|
+
connection = Sequel.connect(uri_no_database.to_s)
|
|
115
|
+
puts [
|
|
116
|
+
"Database",
|
|
117
|
+
theme.code.render(database_name),
|
|
118
|
+
"does not exist. Creating...",
|
|
119
|
+
].join(" ")
|
|
120
|
+
connection.run("CREATE DATABASE \"#{database_name}\"")
|
|
121
|
+
connection.disconnect
|
|
122
|
+
puts [
|
|
123
|
+
theme.success.render("✅ Database"),
|
|
124
|
+
theme.code.render(database_name),
|
|
125
|
+
theme.success.render("created"),
|
|
126
|
+
].join(" ")
|
|
127
|
+
0
|
|
128
|
+
rescue Sequel::DatabaseConnectionError => ex2
|
|
129
|
+
puts [
|
|
130
|
+
theme.error.render("Database server is not running at"),
|
|
131
|
+
theme.code.render(uri_no_database.to_s),
|
|
132
|
+
].join(" ")
|
|
133
|
+
puts [
|
|
134
|
+
theme.error.render(ex2.class.name),
|
|
135
|
+
theme.exception.render(ex2.message),
|
|
136
|
+
].join(": ")
|
|
137
|
+
|
|
138
|
+
puts theme.error.render("This could be a problem with your dev environment")
|
|
139
|
+
puts [
|
|
140
|
+
theme.error.render("Check"),
|
|
141
|
+
theme.code.render(".env.test"),
|
|
142
|
+
theme.error.render("and"),
|
|
143
|
+
theme.code.render(".env.test.local"),
|
|
144
|
+
theme.error.render("to see if "),
|
|
145
|
+
theme.code.render("DATABASE_URL"),
|
|
146
|
+
theme.error.render("is set correctly"),
|
|
147
|
+
].join(" ")
|
|
148
|
+
1
|
|
149
|
+
end
|
|
82
150
|
end
|
|
83
151
|
end
|
|
84
152
|
end
|
|
@@ -94,20 +162,46 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
94
162
|
uri_no_database.path = ""
|
|
95
163
|
begin
|
|
96
164
|
Brut.container.sequel_db_handle.disconnect
|
|
97
|
-
|
|
165
|
+
puts "Database #{theme.code.render(database_name)} exists. Dropping..."
|
|
98
166
|
connection = Sequel.connect(uri_no_database.to_s)
|
|
99
167
|
connection.run("DROP DATABASE IF EXISTS \"#{database_name}\"")
|
|
100
168
|
connection.disconnect
|
|
169
|
+
puts [
|
|
170
|
+
theme.success.render("✅ Database"),
|
|
171
|
+
theme.code.render(database_name),
|
|
172
|
+
theme.success.render("dropped"),
|
|
173
|
+
].join(" ")
|
|
101
174
|
0
|
|
102
175
|
rescue Sequel::DatabaseConnectionError => ex
|
|
103
176
|
begin
|
|
104
177
|
connection = Sequel.connect(uri_no_database.to_s)
|
|
105
|
-
|
|
178
|
+
puts [
|
|
179
|
+
theme.success.render("✅ Database"),
|
|
180
|
+
theme.code.render(database_name),
|
|
181
|
+
theme.success.render("has already been dropped"),
|
|
182
|
+
].join(" ")
|
|
106
183
|
connection.disconnect
|
|
107
184
|
0
|
|
108
185
|
rescue Sequel::DatabaseConnectionError => ex2
|
|
109
|
-
|
|
110
|
-
|
|
186
|
+
puts [
|
|
187
|
+
theme.error.render("Database server is not running at"),
|
|
188
|
+
theme.code.render(uri_no_database.to_s),
|
|
189
|
+
].join(" ")
|
|
190
|
+
puts [
|
|
191
|
+
theme.error.render(ex2.class.name),
|
|
192
|
+
theme.exception.render(ex2.message),
|
|
193
|
+
].join(": ")
|
|
194
|
+
|
|
195
|
+
puts theme.error.render("This could be a problem with your dev environment")
|
|
196
|
+
puts [
|
|
197
|
+
theme.error.render("Check"),
|
|
198
|
+
theme.code.render(".env.test"),
|
|
199
|
+
theme.error.render("and"),
|
|
200
|
+
theme.code.render(".env.test.local"),
|
|
201
|
+
theme.error.render("to see if "),
|
|
202
|
+
theme.code.render("DATABASE_URL"),
|
|
203
|
+
theme.error.render("is set correctly"),
|
|
204
|
+
].join(" ")
|
|
111
205
|
1
|
|
112
206
|
end
|
|
113
207
|
end
|
|
@@ -118,13 +212,30 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
118
212
|
def description = "Apply any outstanding migrations to the database"
|
|
119
213
|
def default_rack_env = "development"
|
|
120
214
|
|
|
215
|
+
class MessagingProxyLogger < SimpleDelegator
|
|
216
|
+
def initialize(logger, command)
|
|
217
|
+
super(logger)
|
|
218
|
+
@command = command
|
|
219
|
+
end
|
|
220
|
+
def info(msg)
|
|
221
|
+
if msg =~ /Finished applying migration (.*).rb/
|
|
222
|
+
@command.send(:puts,"Applied migration #{@command.send(:theme).code.render($1)}")
|
|
223
|
+
end
|
|
224
|
+
__getobj__.info(msg)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def opts = [
|
|
229
|
+
[ "--[no-]sequel-log", "Log Sequel activity at same level as --log-level. When disabled, Sequel will not log at all." ],
|
|
230
|
+
]
|
|
231
|
+
|
|
121
232
|
def run
|
|
122
233
|
migrations_dir = Brut.container.migrations_dir
|
|
123
234
|
if !migrations_dir.exist?
|
|
124
|
-
|
|
235
|
+
puts "No migrations to run from #{migrations_dir}"
|
|
125
236
|
return 0
|
|
126
237
|
elsif Dir[migrations_dir / "*.rb"].empty?
|
|
127
|
-
|
|
238
|
+
puts "No migrations to run from #{migrations_dir}"
|
|
128
239
|
return 0
|
|
129
240
|
end
|
|
130
241
|
|
|
@@ -132,25 +243,24 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
132
243
|
Brut.container.sequel_db_handle.extension :brut_migrations
|
|
133
244
|
Brut.container.sequel_db_handle.extension :pg_array
|
|
134
245
|
|
|
135
|
-
logger =
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
formatted = "#{indent} - #{message}\n"
|
|
140
|
-
if message =~ /^Begin applying/
|
|
141
|
-
indent = " "
|
|
142
|
-
elsif message =~ /^Finished applying/
|
|
143
|
-
indent = ""
|
|
144
|
-
formatted = "#{indent} - #{message}\n"
|
|
145
|
-
end
|
|
146
|
-
formatted
|
|
147
|
-
}
|
|
148
|
-
Brut.container.sequel_db_handle.logger = logger
|
|
246
|
+
Brut.container.sequel_db_handle.logger = MessagingProxyLogger.new(
|
|
247
|
+
execution_context.logger.without_stderr,
|
|
248
|
+
self
|
|
249
|
+
)
|
|
149
250
|
Sequel::Migrator.run(Brut.container.sequel_db_handle,migrations_dir)
|
|
150
|
-
|
|
251
|
+
puts theme.success.render("✅ All migrations have been applied")
|
|
151
252
|
0
|
|
152
253
|
rescue Sequel::DatabaseConnectionError => ex
|
|
153
|
-
|
|
254
|
+
database_name = URI(Brut.container.database_url).path.gsub(/^\//,"")
|
|
255
|
+
puts [
|
|
256
|
+
theme.error.render("Database"),
|
|
257
|
+
theme.code.render(database_name),
|
|
258
|
+
theme.error.render("does not exist."),
|
|
259
|
+
].join(" ")
|
|
260
|
+
puts [
|
|
261
|
+
theme.warning.render("Create it first with"),
|
|
262
|
+
theme.code.render("brut db create"),
|
|
263
|
+
].join(" ")
|
|
154
264
|
1
|
|
155
265
|
rescue Sequel::DatabaseError => ex
|
|
156
266
|
#if ex.cause.kind_of?(PG::UndefinedTable)
|
|
@@ -183,61 +293,71 @@ class Brut::CLI::Apps::DB < Brut::CLI::Commands::BaseCommand
|
|
|
183
293
|
|
|
184
294
|
def run
|
|
185
295
|
seeds_dir = Brut.container.db_seeds_dir
|
|
296
|
+
info "Using seeds from #{seeds_dir}"
|
|
186
297
|
Dir["#{seeds_dir}/*.rb"].each do |file|
|
|
298
|
+
info "Loading seed file #{file}"
|
|
299
|
+
friendly_filename = Pathname(file).relative_path_from(Brut.container.project_root)
|
|
300
|
+
puts "Loading seed data from #{theme.code.render(friendly_filename.to_s)}"
|
|
187
301
|
require file
|
|
188
302
|
end
|
|
189
303
|
seed_data = Brut::BackEnd::SeedData.new
|
|
190
304
|
seed_data.setup!
|
|
191
305
|
seed_data.load_seeds!
|
|
306
|
+
puts theme.success.render("✅ Seed data loaded")
|
|
192
307
|
0
|
|
193
|
-
rescue Sequel::DatabaseConnectionError => ex
|
|
194
|
-
stderr.puts "Database doesn't exist. Create it with `brut db create`"
|
|
195
|
-
1
|
|
196
308
|
rescue Sequel::UniqueConstraintViolation => ex
|
|
197
|
-
|
|
309
|
+
puts theme.error.render("Seed data may have already been loaded:")
|
|
310
|
+
puts theme.exception.render(" #{ex}".strip)
|
|
311
|
+
puts [
|
|
312
|
+
theme.error.render("You can re-load it using"),
|
|
313
|
+
theme.code.render("brut db rebuild && brut db seed"),
|
|
314
|
+
].join(" ")
|
|
198
315
|
1
|
|
199
|
-
rescue Sequel::DatabaseError => ex
|
|
200
|
-
if ex.cause.kind_of?(PG::UndefinedTable)
|
|
201
|
-
stderr.puts "Migrations need to be run. Use `brut db migrate` to run them"
|
|
202
|
-
1
|
|
203
|
-
else
|
|
204
|
-
raise ex
|
|
205
|
-
end
|
|
206
316
|
end
|
|
207
317
|
end
|
|
208
318
|
|
|
209
319
|
class NewMigration < Brut::CLI::Commands::BaseCommand
|
|
210
320
|
def description = "Create a new migration file"
|
|
321
|
+
def opts = [
|
|
322
|
+
[ "--dry-run", "If true, only show what would happen, don't make any files" ],
|
|
323
|
+
]
|
|
211
324
|
def args_description = "migration_name"
|
|
212
325
|
def bootstrap? = false
|
|
213
|
-
|
|
214
|
-
def before_execute
|
|
215
|
-
ENV["RACK_ENV"] = "development"
|
|
216
|
-
end
|
|
326
|
+
def default_rack_env = "development"
|
|
217
327
|
|
|
218
328
|
def run
|
|
219
329
|
if argv.length == 0
|
|
220
|
-
|
|
330
|
+
puts theme.error.render("You must provide a name for the migration")
|
|
221
331
|
return 1
|
|
222
332
|
end
|
|
223
333
|
if env["RACK_ENV"] != "development"
|
|
224
|
-
|
|
334
|
+
puts theme.error.render("This only works in the development environment, not #{theme.code.render(env["RACK_ENV"])}")
|
|
225
335
|
return 1
|
|
226
336
|
end
|
|
227
337
|
migrations_dir = Brut.container.migrations_dir
|
|
228
338
|
name = argv.join(" ").gsub(/[^\w\d\-]/,"-")
|
|
229
339
|
date = DateTime.now.strftime("%Y%m%d%H%M%S")
|
|
230
340
|
file_name = migrations_dir / "#{date}_#{name}.rb"
|
|
231
|
-
File.open(file_name,"w") do |file|
|
|
232
|
-
file.puts "Sequel.migration do"
|
|
233
|
-
file.puts " up do"
|
|
234
|
-
file.puts " # See https://brutrb.com/recipes/migrations.html"
|
|
235
|
-
file.puts " # for a recipe on writing migrations"
|
|
236
|
-
file.puts " end"
|
|
237
|
-
file.puts "end"
|
|
238
|
-
end
|
|
239
341
|
relative_path = file_name.relative_path_from(Brut.container.project_root)
|
|
240
|
-
|
|
342
|
+
puts "Creating new migration file at #{theme.code.render(relative_path.to_s)}"
|
|
343
|
+
info "Creating new migration file at #{file_name}"
|
|
344
|
+
code = %{
|
|
345
|
+
Sequel.migration do
|
|
346
|
+
up do
|
|
347
|
+
# See https://brutrb.com/recipes/migrations.html
|
|
348
|
+
# for a recipe on writing migrations
|
|
349
|
+
end
|
|
350
|
+
end
|
|
351
|
+
}.strip
|
|
352
|
+
if options.dry_run?
|
|
353
|
+
puts theme.warning.render("Dry run - migration would contain this code:")
|
|
354
|
+
puts theme.code.render(code)
|
|
355
|
+
else
|
|
356
|
+
File.open(file_name,"w") do |file|
|
|
357
|
+
file.puts code
|
|
358
|
+
end
|
|
359
|
+
puts theme.success.render("✅ Migration created")
|
|
360
|
+
end
|
|
241
361
|
0
|
|
242
362
|
end
|
|
243
363
|
end
|