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
|
@@ -3,19 +3,22 @@ require "brut/cli"
|
|
|
3
3
|
|
|
4
4
|
class Brut::CLI::Apps::Scaffold < Brut::CLI::Commands::BaseCommand
|
|
5
5
|
def description = "Create scaffolds of various files to help develop more quckly"
|
|
6
|
-
def opts = [
|
|
7
|
-
[ "--overwrite", "If set, any files that exists already will be overwritten by new scaffolds" ],
|
|
8
|
-
[ "--dry-run", "If set, no files are changed. You will see output of what would happen without this flag"],
|
|
9
|
-
]
|
|
10
6
|
|
|
11
|
-
|
|
7
|
+
class BaseCommand < Brut::CLI::Commands::BaseCommand
|
|
8
|
+
def bootstrap? = false
|
|
9
|
+
def default_rack_env = "development"
|
|
10
|
+
def opts = [
|
|
11
|
+
[ "--overwrite", "If set, any files that exists already will be overwritten by new scaffolds" ],
|
|
12
|
+
[ "--dry-run", "If set, no files are changed. You will see output of what would happen without this flag"],
|
|
13
|
+
]
|
|
14
|
+
end
|
|
12
15
|
|
|
13
|
-
class Test <
|
|
16
|
+
class Test < BaseCommand
|
|
14
17
|
def description = "Create the shell of a unit test based on an existing source file"
|
|
15
18
|
def args_description = "source_file_paths..."
|
|
16
19
|
def run
|
|
17
20
|
if argv.empty?
|
|
18
|
-
|
|
21
|
+
error "'test' requires one or more files to scaffold a test for"
|
|
19
22
|
return 1
|
|
20
23
|
end
|
|
21
24
|
files_to_test_files = argv.map { |arg|
|
|
@@ -31,18 +34,18 @@ class Brut::CLI::Apps::Scaffold < Brut::CLI::Commands::BaseCommand
|
|
|
31
34
|
|
|
32
35
|
if non_existent_sources.any?
|
|
33
36
|
relative_paths = non_existent_sources.map { |pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
34
|
-
|
|
37
|
+
error "Not all input files exist:"
|
|
35
38
|
relative_paths.each do |file|
|
|
36
|
-
|
|
39
|
+
error file
|
|
37
40
|
end
|
|
38
41
|
return 1
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
if existent_destinations.any? && !
|
|
44
|
+
if existent_destinations.any? && !options.overwrite?
|
|
42
45
|
relative_paths = existent_destinations.map { |pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
43
|
-
|
|
46
|
+
error "Some files to be generated already exist. Set --overwrite to overwrite them:"
|
|
44
47
|
relative_paths.each do |file|
|
|
45
|
-
|
|
48
|
+
error file
|
|
46
49
|
end
|
|
47
50
|
return 1
|
|
48
51
|
end
|
|
@@ -57,7 +60,7 @@ class Brut::CLI::Apps::Scaffold < Brut::CLI::Commands::BaseCommand
|
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
puts "#{destination} will contain tests for:\n#{classes.join("\n")}\n\n"
|
|
61
64
|
|
|
62
65
|
code = ["require \"spec_helper\"\n"] + classes.map { |class_name|
|
|
63
66
|
%{RSpec.describe #{class_name} do
|
|
@@ -67,8 +70,8 @@ class Brut::CLI::Apps::Scaffold < Brut::CLI::Commands::BaseCommand
|
|
|
67
70
|
end}
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
if
|
|
71
|
-
|
|
73
|
+
if options.dry_run?
|
|
74
|
+
puts code
|
|
72
75
|
else
|
|
73
76
|
FileUtils.mkdir_p destination.dirname
|
|
74
77
|
File.open(destination,"w") do |file|
|
|
@@ -103,10 +106,10 @@ end}
|
|
|
103
106
|
end
|
|
104
107
|
end
|
|
105
108
|
|
|
106
|
-
class E2ETest <
|
|
109
|
+
class E2ETest < BaseCommand
|
|
107
110
|
def description = "Create the shell of an end-to-end test"
|
|
108
111
|
def args_description = "test_name"
|
|
109
|
-
def
|
|
112
|
+
def name = "e2e_test"
|
|
110
113
|
|
|
111
114
|
def opts = [
|
|
112
115
|
["--path PATH","Path within the e2e tests to create the file"],
|
|
@@ -114,7 +117,7 @@ end}
|
|
|
114
117
|
|
|
115
118
|
def run
|
|
116
119
|
if argv.empty?
|
|
117
|
-
|
|
120
|
+
error "'#{self.class.command_name}' requires a name"
|
|
118
121
|
return 1
|
|
119
122
|
end
|
|
120
123
|
test_name = argv.join(" ").gsub(/\"/,"'")
|
|
@@ -130,11 +133,11 @@ end}
|
|
|
130
133
|
dry_run_verb = "create"
|
|
131
134
|
|
|
132
135
|
if path_to_test_file.exist?
|
|
133
|
-
if
|
|
136
|
+
if options.overwrite?
|
|
134
137
|
verb = "Overwrote"
|
|
135
138
|
dry_run_verb = "overwrite"
|
|
136
139
|
else
|
|
137
|
-
|
|
140
|
+
error "#{path_to_test_file.relative_path_from(Brut.container.project_root)} exists. Use --overwrite to replace it"
|
|
138
141
|
return 1
|
|
139
142
|
end
|
|
140
143
|
end
|
|
@@ -148,16 +151,16 @@ RSpec.describe "#{test_name}" do
|
|
|
148
151
|
expect(page).to be_page_for(page_class_here)
|
|
149
152
|
end
|
|
150
153
|
end}
|
|
151
|
-
if
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
if options.dry_run?
|
|
155
|
+
puts "Will #{dry_run_verb} #{path_to_test_file.relative_path_from(Brut.container.project_root)} with this code:"
|
|
156
|
+
puts
|
|
157
|
+
puts code
|
|
155
158
|
else
|
|
156
159
|
FileUtils.mkdir_p test_file_dir
|
|
157
160
|
File.open(path_to_test_file,"w") do |file|
|
|
158
161
|
file.puts code
|
|
159
162
|
end
|
|
160
|
-
|
|
163
|
+
puts "#{verb} #{path_to_test_file.relative_path_from(Brut.container.project_root)}"
|
|
161
164
|
end
|
|
162
165
|
0
|
|
163
166
|
end
|
|
@@ -185,7 +188,7 @@ end}
|
|
|
185
188
|
end
|
|
186
189
|
end
|
|
187
190
|
|
|
188
|
-
class Component <
|
|
191
|
+
class Component < BaseCommand
|
|
189
192
|
def description = "Create a new component and associated test"
|
|
190
193
|
def args_description = "ComponentName"
|
|
191
194
|
def detailed_description = "New components go in the `components/` folder of your app, however using --page will create a 'page private' component. To do that, the component name must be an inner class of an existing page, for example HomePage::Welcome. This component goes in a sub-folder inside the `pages/` area of your app"
|
|
@@ -230,17 +233,17 @@ end}
|
|
|
230
233
|
spec_path,
|
|
231
234
|
].select(&:exist?)
|
|
232
235
|
|
|
233
|
-
if exists.any? && !
|
|
236
|
+
if exists.any? && !options.overwrite?
|
|
234
237
|
exists.each do |path|
|
|
235
|
-
|
|
238
|
+
error "'#{path.relative_path_from(Brut.container.project_root)}' exists already"
|
|
236
239
|
end
|
|
237
|
-
|
|
240
|
+
error "Re-run with --overwrite to overwrite these files"
|
|
238
241
|
return 1
|
|
239
242
|
end
|
|
240
243
|
|
|
241
|
-
if
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
if options.dry_run?
|
|
245
|
+
puts "FileUtils.mkdir_p #{source_path.dirname}"
|
|
246
|
+
puts "FileUtils.mkdir_p #{spec_path.dirname}"
|
|
244
247
|
else
|
|
245
248
|
FileUtils.mkdir_p source_path.dirname
|
|
246
249
|
FileUtils.mkdir_p spec_path.dirname
|
|
@@ -265,12 +268,12 @@ RSpec.describe #{class_name} do
|
|
|
265
268
|
end}
|
|
266
269
|
end
|
|
267
270
|
end
|
|
268
|
-
|
|
269
|
-
|
|
271
|
+
puts "Component source is in #{source_path.relative_path_from(Brut.container.project_root)}"
|
|
272
|
+
puts "Component test is in #{spec_path.relative_path_from(Brut.container.project_root)}"
|
|
270
273
|
0
|
|
271
274
|
end
|
|
272
275
|
end
|
|
273
|
-
class Page <
|
|
276
|
+
class Page < BaseCommand
|
|
274
277
|
class Route < Brut::FrontEnd::Routing::PageRoute
|
|
275
278
|
def initialize(path_template)
|
|
276
279
|
path_template = "/#{path_template}".gsub(/\/\//,"/")
|
|
@@ -310,16 +313,16 @@ end}
|
|
|
310
313
|
page_spec_path,
|
|
311
314
|
].select(&:exist?)
|
|
312
315
|
|
|
313
|
-
if exists.any? && !
|
|
316
|
+
if exists.any? && !options.overwrite?
|
|
314
317
|
exists.each do |path|
|
|
315
|
-
|
|
318
|
+
error "'#{path.relative_path_from(Brut.container.project_root)}' exists already"
|
|
316
319
|
end
|
|
317
|
-
|
|
320
|
+
error "Re-run with --overwrite to overwrite these files"
|
|
318
321
|
return 1
|
|
319
322
|
end
|
|
320
323
|
|
|
321
|
-
FileUtils.mkdir_p page_source_path.dirname, noop:
|
|
322
|
-
FileUtils.mkdir_p page_spec_path.dirname, noop:
|
|
324
|
+
FileUtils.mkdir_p page_source_path.dirname, noop: options.dry_run?
|
|
325
|
+
FileUtils.mkdir_p page_spec_path.dirname, noop: options.dry_run?
|
|
323
326
|
|
|
324
327
|
route_code = "page \"#{route.path_template}\""
|
|
325
328
|
|
|
@@ -349,15 +352,15 @@ end}
|
|
|
349
352
|
title = RichString.new(page_class_name).underscorized.humanized.to_s.capitalize
|
|
350
353
|
translations_code = " \"#{page_class_name}\": {\n title: \"#{title}\",\n \},"
|
|
351
354
|
|
|
352
|
-
if
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
355
|
+
if options.dry_run?
|
|
356
|
+
puts app_path.relative_path_from(Brut.container.project_root)
|
|
357
|
+
puts "will contain:\n\n#{route_code}\n\n"
|
|
358
|
+
puts page_source_path.relative_path_from(Brut.container.project_root)
|
|
359
|
+
puts "will contain:\n\n#{page_class_code}\n\n"
|
|
360
|
+
puts page_spec_path.relative_path_from(Brut.container.project_root)
|
|
361
|
+
puts "will contain:\n\n#{page_spec_code}\n\n"
|
|
362
|
+
puts app_translations.relative_path_from(Brut.container.project_root)
|
|
363
|
+
puts "will contain:\n\n#{translations_code}\n\n"
|
|
361
364
|
else
|
|
362
365
|
|
|
363
366
|
File.open(page_source_path,"w") { it.puts page_class_code }
|
|
@@ -377,29 +380,29 @@ end}
|
|
|
377
380
|
end
|
|
378
381
|
end
|
|
379
382
|
if !inserted_translation
|
|
380
|
-
|
|
381
|
-
|
|
383
|
+
error "WARNING: Could not find a place to insert the translation for this page's title"
|
|
384
|
+
error " The page may not render properly the first time you load it"
|
|
382
385
|
end
|
|
383
386
|
|
|
384
387
|
routes_editor = RoutesEditor.new(app_path:,out:)
|
|
385
388
|
routes_editor.add_route!(route_code:)
|
|
386
389
|
|
|
387
390
|
if !routes_editor.found_routes?
|
|
388
|
-
|
|
389
|
-
|
|
391
|
+
puts "Could not find routes declaration in #{app_path.relative_path_from(Brut.container.project_root)}"
|
|
392
|
+
puts "Please add this to wherever you have defined your routes:\n\n#{route_code}\n\n"
|
|
390
393
|
elsif routes_editor.routes_existed?
|
|
391
|
-
|
|
392
|
-
|
|
394
|
+
puts "Routes declaration in #{app_path.relative_path_from(Brut.container.project_root)} contained the route defition already"
|
|
395
|
+
puts "Please make sure everything is correct. Here is the defintion that was not inserted:\n\n#{route_code}"
|
|
393
396
|
end
|
|
394
397
|
end
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
398
|
+
puts "Page source is in #{page_source_path.relative_path_from(Brut.container.project_root)}"
|
|
399
|
+
puts "Page test is in #{page_spec_path.relative_path_from(Brut.container.project_root)}"
|
|
400
|
+
puts "Added title to #{app_translations.relative_path_from(Brut.container.project_root)}"
|
|
401
|
+
puts "Added route to #{app_path.relative_path_from(Brut.container.project_root)}"
|
|
399
402
|
0
|
|
400
403
|
end
|
|
401
404
|
end
|
|
402
|
-
class Action <
|
|
405
|
+
class Action < BaseCommand
|
|
403
406
|
class Route < Brut::FrontEnd::Routing::FormRoute
|
|
404
407
|
def initialize(path_template)
|
|
405
408
|
path_template = "/#{path_template}".gsub(/\/\//,"/")
|
|
@@ -451,19 +454,19 @@ end}
|
|
|
451
454
|
|
|
452
455
|
exists = paths_to_check.select(&:exist?)
|
|
453
456
|
|
|
454
|
-
if exists.any? && !
|
|
457
|
+
if exists.any? && !options.overwrite?
|
|
455
458
|
exists.each do |path|
|
|
456
|
-
|
|
459
|
+
error "'#{path.relative_path_from(Brut.container.project_root)}' exists already"
|
|
457
460
|
end
|
|
458
|
-
|
|
461
|
+
error "Re-run with global option --overwrite to overwrite these files"
|
|
459
462
|
return 1
|
|
460
463
|
end
|
|
461
464
|
|
|
462
465
|
if form
|
|
463
|
-
FileUtils.mkdir_p form_source_path.dirname, noop:
|
|
466
|
+
FileUtils.mkdir_p form_source_path.dirname, noop: options.dry_run?
|
|
464
467
|
end
|
|
465
|
-
FileUtils.mkdir_p handler_source_path.dirname, noop:
|
|
466
|
-
FileUtils.mkdir_p handler_spec_path.dirname, noop:
|
|
468
|
+
FileUtils.mkdir_p handler_source_path.dirname, noop: options.dry_run?
|
|
469
|
+
FileUtils.mkdir_p handler_spec_path.dirname, noop: options.dry_run?
|
|
467
470
|
|
|
468
471
|
form_code = %{class #{form_class_name} < AppForm
|
|
469
472
|
input :some_field, minlength: 3
|
|
@@ -504,26 +507,26 @@ end}
|
|
|
504
507
|
"path \"#{route.path_template}\", method: :#{options.http_method.downcase}"
|
|
505
508
|
end
|
|
506
509
|
|
|
507
|
-
if
|
|
508
|
-
|
|
509
|
-
|
|
510
|
+
if options.dry_run?
|
|
511
|
+
puts app_path.relative_path_from(Brut.container.project_root)
|
|
512
|
+
puts "will contain:\n\n#{route_code}\n\n"
|
|
510
513
|
if form
|
|
511
|
-
|
|
512
|
-
|
|
514
|
+
puts form_source_path.relative_path_from(Brut.container.project_root)
|
|
515
|
+
puts "will contain:\n\n#{form_code}\n\n"
|
|
513
516
|
end
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
517
|
+
puts handler_source_path.relative_path_from(Brut.container.project_root)
|
|
518
|
+
puts "will contain:\n\n#{handler_code}\n\n"
|
|
519
|
+
puts handler_spec_path.relative_path_from(Brut.container.project_root)
|
|
520
|
+
puts "will contain:\n\n#{spec_code}\n\n"
|
|
518
521
|
else
|
|
519
522
|
class_name_length = [ form_class_name.length, handler_class_name.length, "Spec".length ].max
|
|
520
523
|
printf_string = "%-#{class_name_length}s in %s\n"
|
|
521
|
-
|
|
524
|
+
puts "\n\n"
|
|
522
525
|
if form
|
|
523
|
-
stdout.printf printf_string,form_class_name,form_source_path.relative_path_from(Brut.container.project_root)
|
|
526
|
+
execution_context.stdout.printf printf_string,form_class_name,form_source_path.relative_path_from(Brut.container.project_root)
|
|
524
527
|
end
|
|
525
|
-
stdout.printf printf_string,handler_class_name, handler_source_path.relative_path_from(Brut.container.project_root)
|
|
526
|
-
stdout.printf printf_string,"Spec", handler_spec_path.relative_path_from(Brut.container.project_root)
|
|
528
|
+
execution_context.stdout.printf printf_string,handler_class_name, handler_source_path.relative_path_from(Brut.container.project_root)
|
|
529
|
+
execution_context.stdout.printf printf_string,"Spec", handler_spec_path.relative_path_from(Brut.container.project_root)
|
|
527
530
|
|
|
528
531
|
routes_editor = RoutesEditor.new(app_path:,out:)
|
|
529
532
|
routes_editor.add_route!(route_code:)
|
|
@@ -534,11 +537,11 @@ end}
|
|
|
534
537
|
File.open(handler_source_path,"w") { it.puts handler_code }
|
|
535
538
|
File.open(handler_spec_path,"w") { it.puts spec_code }
|
|
536
539
|
if !routes_editor.found_routes?
|
|
537
|
-
|
|
538
|
-
|
|
540
|
+
puts "Could not find routes declaration in #{app_path.relative_path_from(Brut.container.project_root)}"
|
|
541
|
+
puts "Please add this to wherever you have defined your routes:\n\n#{route_code}\n\n"
|
|
539
542
|
elsif routes_editor.routes_existed?
|
|
540
|
-
|
|
541
|
-
|
|
543
|
+
puts "Routes declaration in #{app_path.relative_path_from(Brut.container.project_root)} contained the route defition already"
|
|
544
|
+
puts "Please make sure everything is correct. Here is the defintion that was not inserted:\n\n#{route_code}"
|
|
542
545
|
end
|
|
543
546
|
end
|
|
544
547
|
0
|
|
@@ -554,17 +557,17 @@ end}
|
|
|
554
557
|
end
|
|
555
558
|
end
|
|
556
559
|
|
|
557
|
-
class CustomElementTest <
|
|
560
|
+
class CustomElementTest < BaseCommand
|
|
558
561
|
def description = "Create a test for a custom element in your app"
|
|
559
562
|
def args_description = "path_to_js_files..."
|
|
560
563
|
def run
|
|
561
564
|
if argv.empty?
|
|
562
|
-
|
|
565
|
+
error "'custom-element-test' requires one or more files to scaffold a test for"
|
|
563
566
|
return 1
|
|
564
567
|
end
|
|
565
568
|
|
|
566
569
|
if argv.any? { |file| Pathname(file).extname != ".js" }
|
|
567
|
-
|
|
570
|
+
error "'custom-element-test' must be given only .js files"
|
|
568
571
|
return 1
|
|
569
572
|
end
|
|
570
573
|
|
|
@@ -580,11 +583,11 @@ end}
|
|
|
580
583
|
spec.exist?
|
|
581
584
|
}
|
|
582
585
|
|
|
583
|
-
if existing_files.any? && !
|
|
586
|
+
if existing_files.any? && !options.overwrite?
|
|
584
587
|
relative_paths = existing_files.map { |_,pathname| pathname.relative_path_from(Brut.container.project_root) }
|
|
585
|
-
|
|
588
|
+
error "Some files to be generated already exist. Set global option --overwrite to overwrite them:"
|
|
586
589
|
relative_paths.each do |file|
|
|
587
|
-
|
|
590
|
+
error file
|
|
588
591
|
end
|
|
589
592
|
return 1
|
|
590
593
|
end
|
|
@@ -609,8 +612,8 @@ describe("#{description}", () => {
|
|
|
609
612
|
assert.fail("test goes here")
|
|
610
613
|
})
|
|
611
614
|
})}
|
|
612
|
-
if
|
|
613
|
-
|
|
615
|
+
if options.dry_run?
|
|
616
|
+
puts "Would generate this code:\n\n#{code}"
|
|
614
617
|
else
|
|
615
618
|
File.open(spec_file, "w") do |file|
|
|
616
619
|
file.puts code
|
|
@@ -622,7 +625,7 @@ describe("#{description}", () => {
|
|
|
622
625
|
end
|
|
623
626
|
end
|
|
624
627
|
|
|
625
|
-
class DbModel <
|
|
628
|
+
class DbModel < BaseCommand
|
|
626
629
|
def description = "Creates a DB models, factories, and a single placeholder migration"
|
|
627
630
|
def args_description = "model_name..."
|
|
628
631
|
|
|
@@ -648,31 +651,31 @@ describe("#{description}", () => {
|
|
|
648
651
|
}
|
|
649
652
|
end
|
|
650
653
|
migration_name = "create_" + argv.join("_").gsub(/[^\w]/,"_").gsub(/__/,"_")
|
|
651
|
-
if
|
|
652
|
-
|
|
654
|
+
if options.dry_run?
|
|
655
|
+
puts "Would create the following DB models:"
|
|
653
656
|
actions.each do |action|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
657
|
+
puts "#{action[:class_name]}"
|
|
658
|
+
puts " prefix: #{action[:prefix]}"
|
|
659
|
+
puts " in: #{action[:path]}"
|
|
660
|
+
puts " spec: #{action[:spec_path]}"
|
|
661
|
+
puts " factory: #{action[:factory_path]}"
|
|
662
|
+
puts " name: #{action[:factory_name]}"
|
|
660
663
|
|
|
661
664
|
end
|
|
662
|
-
|
|
663
|
-
|
|
665
|
+
puts "Would create a migration file"
|
|
666
|
+
puts " via: brut db new_migration #{migration_name}"
|
|
664
667
|
else
|
|
665
668
|
system!("brut db new_migration #{migration_name}")
|
|
666
669
|
actions.each do |action|
|
|
667
670
|
FileUtils.mkdir_p action[:path].dirname
|
|
668
|
-
|
|
671
|
+
puts "Creating #{action[:class_name]} in #{action[:path].relative_path_from(Brut.container.project_root)}"
|
|
669
672
|
File.open(action[:path].to_s,"w") do |file|
|
|
670
673
|
file.puts %{class #{action[:class_name]} < AppDataModel
|
|
671
674
|
has_external_id :#{action[:prefix]} # !IMPORTANT: Make sure this is unique amongst your DB models
|
|
672
675
|
end}
|
|
673
676
|
end
|
|
674
677
|
FileUtils.mkdir_p action[:spec_path].dirname
|
|
675
|
-
|
|
678
|
+
puts "Creating spec for #{action[:class_name]} in #{action[:spec_path].relative_path_from(Brut.container.project_root)}"
|
|
676
679
|
File.open(action[:spec_path].to_s,"w") do |file|
|
|
677
680
|
file.puts %{require "spec_helper"
|
|
678
681
|
RSpec.describe #{action[:class_name]} do
|
|
@@ -682,7 +685,7 @@ RSpec.describe #{action[:class_name]} do
|
|
|
682
685
|
end}
|
|
683
686
|
end
|
|
684
687
|
FileUtils.mkdir_p action[:factory_path].dirname
|
|
685
|
-
|
|
688
|
+
puts "Creating factory for #{action[:class_name]} in #{action[:factory_path].relative_path_from(Brut.container.project_root)}"
|
|
686
689
|
File.open(action[:factory_path].to_s,"w") do |file|
|
|
687
690
|
file.puts %{FactoryBot.define do
|
|
688
691
|
factory :#{action[:factory_name]}, class: "#{action[:class_name]}" do
|
data/lib/brut/cli/apps/test.rb
CHANGED
|
@@ -5,7 +5,11 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
5
5
|
def description = "Run and audit tests of the app"
|
|
6
6
|
|
|
7
7
|
def default_rack_env = "development"
|
|
8
|
-
def
|
|
8
|
+
def default_command = Run.new
|
|
9
|
+
def opts = default_command.opts
|
|
10
|
+
def run
|
|
11
|
+
delegate_to_command(default_command)
|
|
12
|
+
end
|
|
9
13
|
|
|
10
14
|
class Run < Brut::CLI::Commands::BaseCommand
|
|
11
15
|
def default_rack_env = "development"
|
|
@@ -16,6 +20,9 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
16
20
|
[ "--seed SEED", "Set the random seed to allow duplicating a test run" ],
|
|
17
21
|
]
|
|
18
22
|
def args_description = "specs_to_run..."
|
|
23
|
+
def detailed_description = %{
|
|
24
|
+
Runs all non end-to-end tests for the app, or runs a subset of non-end-to-end tests using RSpec-style syntax. Do note that you cannot use this command to run an end-to-end test, since those require the test server to be running.
|
|
25
|
+
}
|
|
19
26
|
|
|
20
27
|
def env_vars = [
|
|
21
28
|
[ "LOGGER_LEVEL_FOR_TESTS","Can be set to debug, info, warn, error, or fatal to control logging during tests. Defaults to 'warn' to avoid verbose test output" ],
|
|
@@ -23,13 +30,11 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
23
30
|
[ "RSPEC_PROFILE_EXAMPLES", "If set to any value, it is converted to an int and set as RSpec's number of examples to profile. NOTE: this is used in the app's spec_helper.rb so could've been removed" ],
|
|
24
31
|
]
|
|
25
32
|
|
|
26
|
-
|
|
27
33
|
def rspec_command
|
|
28
34
|
parts = [
|
|
29
35
|
"bin/rspec",
|
|
30
36
|
"-I", Brut.container.app_specs_dir,
|
|
31
37
|
"-I", Brut.container.app_src_dir,
|
|
32
|
-
#"-I lib/", # not needed when Brut is gemified
|
|
33
38
|
rspec_cli_args,
|
|
34
39
|
"-P '**/*.spec.rb'",
|
|
35
40
|
]
|
|
@@ -46,14 +51,14 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
46
51
|
|
|
47
52
|
def run
|
|
48
53
|
if options.rebuild?(default: rebuild_by_default?)
|
|
49
|
-
|
|
54
|
+
puts "Rebuilding test database schema"
|
|
50
55
|
Bundler.with_unbundled_env do
|
|
51
56
|
system! "brut db rebuild --env=test"
|
|
52
57
|
end
|
|
53
58
|
end
|
|
54
59
|
run_tests
|
|
55
60
|
if options.rebuild_after?(default: rebuild_after_by_default?)
|
|
56
|
-
|
|
61
|
+
puts "Re-Rebuilding test database schema"
|
|
57
62
|
Bundler.with_unbundled_env do
|
|
58
63
|
system! "brut db rebuild --env=test"
|
|
59
64
|
end
|
|
@@ -65,17 +70,17 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
65
70
|
|
|
66
71
|
def run_tests
|
|
67
72
|
command = if argv.empty?
|
|
68
|
-
|
|
73
|
+
puts "Running all unit tests"
|
|
69
74
|
"#{rspec_command} #{Brut.container.app_specs_dir}/"
|
|
70
75
|
else
|
|
71
|
-
|
|
76
|
+
puts "Running only #{argv.join(", ")}"
|
|
72
77
|
test_args = argv.map { |_|
|
|
73
78
|
'"' + Shellwords.escape(_) + '"'
|
|
74
79
|
}.join(" ")
|
|
75
80
|
"#{rspec_command} #{test_args}"
|
|
76
81
|
end
|
|
77
82
|
Bundler.with_unbundled_env do
|
|
78
|
-
system! command
|
|
83
|
+
execution_context.executor.system! command
|
|
79
84
|
end
|
|
80
85
|
end
|
|
81
86
|
end
|
|
@@ -90,6 +95,9 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
90
95
|
def rspec_cli_args = "--tag e2e"
|
|
91
96
|
def rebuild_by_default? = true
|
|
92
97
|
def rebuild_after_by_default? = true
|
|
98
|
+
def detailed_description = %{
|
|
99
|
+
Runs all end-to-end tests for the app, or runs a subset of end-to-end tests using RSpec-style syntax. This will run bin/test-server first, so if that fails for some reason, no tests are run.
|
|
100
|
+
}
|
|
93
101
|
|
|
94
102
|
private
|
|
95
103
|
|
|
@@ -107,6 +115,9 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
107
115
|
def opts = [
|
|
108
116
|
[ "--[no-]build-assets","Build all assets before running the tests" ],
|
|
109
117
|
]
|
|
118
|
+
def detailed_description = %{
|
|
119
|
+
Runs all JavaScript unit tests for the app. This does not support running individual tests.
|
|
120
|
+
}
|
|
110
121
|
|
|
111
122
|
def run
|
|
112
123
|
options.set_default(:"build-assets", true)
|
|
@@ -115,7 +126,7 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
115
126
|
system!({ "RACK_ENV" => "test" }, "brut build-assets all")
|
|
116
127
|
end
|
|
117
128
|
end
|
|
118
|
-
system!({ "NODE_DISABLE_COLORS" => "1" },"npx mocha #{Brut.container.js_specs_dir} --no-color --extension 'spec.js' --recursive")
|
|
129
|
+
execution_context.executor.system!({ "NODE_DISABLE_COLORS" => "1" },"npx mocha #{Brut.container.js_specs_dir} --no-color --extension 'spec.js' --recursive")
|
|
119
130
|
0
|
|
120
131
|
end
|
|
121
132
|
end
|
|
@@ -208,36 +219,44 @@ class Brut::CLI::Apps::Test < Brut::CLI::Commands::BaseCommand
|
|
|
208
219
|
hash
|
|
209
220
|
}.compact.sort_by { it[:type].to_s + it[:source_file].to_s }
|
|
210
221
|
|
|
211
|
-
|
|
212
|
-
printed_header = false
|
|
222
|
+
rows = []
|
|
213
223
|
audit.each do |file_audit|
|
|
214
224
|
if !file_audit[:test_file].exist?
|
|
215
225
|
if options.type.nil? || file_audit[:type] == options.type.to_sym
|
|
216
226
|
if file_audit[:test_expected]
|
|
217
|
-
|
|
218
|
-
if !printed_header
|
|
219
|
-
stdout.puts "These files are missing tests:"
|
|
220
|
-
stdout.puts ""
|
|
221
|
-
stdout.printf "%-25s %s\n","Type", "Path"
|
|
222
|
-
stdout.puts "-------------------------------------------"
|
|
223
|
-
printed_header = true
|
|
224
|
-
end
|
|
225
|
-
stdout.puts "#{file_audit[:type].to_s.ljust(25)} - #{file_audit[:source_file]}"
|
|
227
|
+
rows << [ file_audit[:type].to_s, file_audit[:source_file].to_s ]
|
|
226
228
|
end
|
|
227
229
|
end
|
|
228
230
|
end
|
|
229
231
|
end
|
|
230
|
-
if
|
|
231
|
-
|
|
232
|
+
if rows.empty?
|
|
233
|
+
puts theme.success.render("All tests exists!")
|
|
232
234
|
0
|
|
233
235
|
else
|
|
236
|
+
puts
|
|
237
|
+
puts theme.warning.render("The following source files are missing tests:")
|
|
238
|
+
puts
|
|
239
|
+
table = Lipgloss::Table.new.
|
|
240
|
+
headers(["Type", "Path"]).
|
|
241
|
+
rows(rows).
|
|
242
|
+
style_func(rows: rows.length, columns: 2) { |row,column|
|
|
243
|
+
if row == Lipgloss::Table::HEADER_ROW
|
|
244
|
+
Lipgloss::Style.new.inherit(theme.header).padding_left(1).padding_right(1)
|
|
245
|
+
else
|
|
246
|
+
Lipgloss::Style.new.inherit(theme.none).padding_left(1).padding_right(1)
|
|
247
|
+
end
|
|
248
|
+
}
|
|
249
|
+
puts table.render
|
|
234
250
|
if options.show_scaffold?
|
|
235
|
-
|
|
236
|
-
files_missing_args =
|
|
237
|
-
'
|
|
251
|
+
puts
|
|
252
|
+
files_missing_args = rows.map { |(_,file)|
|
|
253
|
+
' "' + Shellwords.escape(file.to_s) + '"'
|
|
238
254
|
}.join(" \\\n")
|
|
239
255
|
|
|
240
|
-
|
|
256
|
+
puts theme.subheader.render("Run this command to generate empty tests")
|
|
257
|
+
puts
|
|
258
|
+
puts theme.code.render(" brut scaffold test \\\n#{files_missing_args}")
|
|
259
|
+
puts
|
|
241
260
|
end
|
|
242
261
|
1
|
|
243
262
|
end
|