asgard 0.3.0 → 0.3.2
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/.envrc +5 -0
- data/.loki +19 -23
- data/.reek.yml +112 -0
- data/.rubocop.yml +6 -0
- data/Archspec.rb +24 -0
- data/CHANGELOG.md +98 -2
- data/CLAUDE.md +4 -7
- data/README.md +93 -2
- data/bin/asgard +1 -1
- data/docs/api.md +130 -10
- data/docs/changelog.md +28 -0
- data/docs/dependencies.md +83 -2
- data/docs/getting-started.md +4 -2
- data/docs/helpers.md +84 -0
- data/docs/index.md +3 -3
- data/docs/options.md +51 -7
- data/docs/task-files.md +2 -2
- data/examples/bad.loki +63 -0
- data/examples/server_subcommands.loki +31 -22
- data/gem_tasks.loki +51 -0
- data/git.loki +13 -0
- data/lib/asgard/base/dependency_graph.rb +124 -0
- data/lib/asgard/base/dispatch.rb +160 -0
- data/lib/asgard/base/registry.rb +38 -0
- data/lib/asgard/base/task_dsl.rb +65 -0
- data/lib/asgard/base.rb +44 -222
- data/lib/asgard/doctor/report.rb +73 -0
- data/lib/asgard/doctor/task_sections.rb +67 -0
- data/lib/asgard/doctor.rb +150 -0
- data/lib/asgard/kernel_methods.rb +12 -6
- data/lib/asgard/tasks.rb +18 -6
- data/lib/asgard/version.rb +1 -1
- data/lib/asgard.rb +11 -1
- data/quality.loki +261 -0
- data/quality_rails.loki +46 -0
- data/xyzzy.loki +12 -0
- metadata +17 -17
- data/Rakefile +0 -101
data/lib/asgard.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "asgard/kernel_methods"
|
|
|
5
5
|
require_relative "asgard/shell"
|
|
6
6
|
require_relative "asgard/base"
|
|
7
7
|
require_relative "asgard/tasks"
|
|
8
|
+
require_relative "asgard/doctor"
|
|
8
9
|
|
|
9
10
|
module Asgard
|
|
10
11
|
class Error < StandardError; end
|
|
@@ -18,7 +19,16 @@ module Asgard
|
|
|
18
19
|
|
|
19
20
|
# Main entry point invoked by the asgard executable.
|
|
20
21
|
def self.run!(argv)
|
|
21
|
-
|
|
22
|
+
first = argv.first
|
|
23
|
+
abort "asgard: unknown command '#{first}'" if first&.start_with?("_")
|
|
24
|
+
if argv.include?("--version")
|
|
25
|
+
puts Asgard::VERSION
|
|
26
|
+
exit
|
|
27
|
+
end
|
|
28
|
+
if argv.include?("--doctor")
|
|
29
|
+
Asgard::Doctor.new.run
|
|
30
|
+
exit
|
|
31
|
+
end
|
|
22
32
|
task_file = find_task_file or abort "asgard: no .loki file found in #{Dir.pwd}"
|
|
23
33
|
before = Asgard::Base.subclasses.dup
|
|
24
34
|
load task_file
|
data/quality.loki
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Quality gate tasks — imported by .loki
|
|
3
|
+
|
|
4
|
+
class Tasks
|
|
5
|
+
desc "Run the test suite"
|
|
6
|
+
def test_check
|
|
7
|
+
output = `bundle exec ruby -Ilib:test test/test_asgard.rb 2>&1`
|
|
8
|
+
result = $?.success? ? :pass : :fail
|
|
9
|
+
File.write("test_output.txt", output)
|
|
10
|
+
summary = output.lines.reverse.find { |l| l =~ /\d+ runs,/ }&.strip || result.to_s
|
|
11
|
+
puts "Tests: #{summary} (see test_output.txt)"
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "Run the test suite with verbose output"
|
|
16
|
+
def test_verbose
|
|
17
|
+
sh "bundle exec ruby -Ilib:test test/test_asgard.rb -v"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "Run every *_check quality gate task in parallel"
|
|
21
|
+
# A Proc defers resolution to validate_deps!, after every .loki file has
|
|
22
|
+
# loaded — so a *_check task from another file (e.g. quality_rails.loki's
|
|
23
|
+
# brakeman_check) is picked up too, with no need to redeclare anything here.
|
|
24
|
+
depends_on -> { [all_commands.keys.grep(/_check\z/).sort.map(&:to_sym)] }
|
|
25
|
+
def quality
|
|
26
|
+
check_tasks = self.class._deps.fetch(:quality, []).flatten.map(&:to_s)
|
|
27
|
+
|
|
28
|
+
results = check_tasks.to_h { |name| [quality_gate_label(name), dep_result(name)] }
|
|
29
|
+
|
|
30
|
+
print_quality_summary(results)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
desc "Check code style with RuboCop"
|
|
34
|
+
def rubocop_check
|
|
35
|
+
output = `RUBOCOP_CACHE_ROOT=tmp/rubocop_cache bundle exec rubocop 2>&1`
|
|
36
|
+
result = $?.success? ? :pass : :fail
|
|
37
|
+
File.write("rubocop_output.txt", output)
|
|
38
|
+
summary = output.lines.find { |l| l =~ /files inspected/ }&.strip || result.to_s
|
|
39
|
+
puts "RuboCop: #{summary} (see rubocop_output.txt)"
|
|
40
|
+
result
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
desc "Auto-correct RuboCop offenses"
|
|
44
|
+
def rubocop_fix
|
|
45
|
+
sh "RUBOCOP_CACHE_ROOT=tmp/rubocop_cache bundle exec rubocop -a"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
desc "Check code complexity with Flog (warn >=20, fail >=50)"
|
|
49
|
+
def flog_check
|
|
50
|
+
require "flog"
|
|
51
|
+
|
|
52
|
+
method_warn = 20.0
|
|
53
|
+
method_fail = 50.0
|
|
54
|
+
|
|
55
|
+
flogger = Flog.new(all: true)
|
|
56
|
+
flogger.flog(*Dir.glob("lib/**/*.rb"))
|
|
57
|
+
|
|
58
|
+
warnings = []
|
|
59
|
+
failures = []
|
|
60
|
+
|
|
61
|
+
flogger.each_by_score do |method_name, score|
|
|
62
|
+
next if method_name.end_with?("#none")
|
|
63
|
+
if score > method_fail
|
|
64
|
+
failures << "#{"%.1f" % score}: #{method_name}"
|
|
65
|
+
elsif score > method_warn
|
|
66
|
+
warnings << "#{"%.1f" % score}: #{method_name}"
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
result = failures.empty? ? :pass : :fail
|
|
71
|
+
|
|
72
|
+
lines = ["Flog warnings (#{method_warn}–#{method_fail}):"]
|
|
73
|
+
lines.concat(warnings.empty? ? [" (none)"] : warnings.map { |w| " #{w}" })
|
|
74
|
+
lines << ""
|
|
75
|
+
lines << "Flog failures (>= #{method_fail}):"
|
|
76
|
+
lines.concat(failures.empty? ? [" (none)"] : failures.map { |f| " #{f}" })
|
|
77
|
+
File.write("flog_output.txt", "#{lines.join("\n")}\n")
|
|
78
|
+
|
|
79
|
+
puts "Flog: #{failures.size} failure(s), #{warnings.size} warning(s) (see flog_output.txt)"
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
desc "Check for structural code duplication with Flay (mass >= 150)"
|
|
84
|
+
def flay_check
|
|
85
|
+
require "flay"
|
|
86
|
+
|
|
87
|
+
mass_threshold = 150
|
|
88
|
+
|
|
89
|
+
flay = Flay.new(mass: mass_threshold, diff: false, verbose: false, summary: false, timeout: 60)
|
|
90
|
+
flay.process(*Dir.glob("lib/**/*.rb"))
|
|
91
|
+
flay.analyze
|
|
92
|
+
|
|
93
|
+
result = flay.hashes.empty? ? :pass : :fail
|
|
94
|
+
|
|
95
|
+
File.open("flay_output.txt", "w") do |f|
|
|
96
|
+
if result == :pass
|
|
97
|
+
f.puts "Flay: no structural duplication detected (mass >= #{mass_threshold})"
|
|
98
|
+
else
|
|
99
|
+
flay.report(f)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
puts "Flay: #{flay.hashes.size} duplication pattern(s) found (mass >= #{mass_threshold}) (see flay_output.txt)"
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
desc "Check code smells with Reek"
|
|
107
|
+
def reek_check
|
|
108
|
+
output = `bundle exec reek lib 2>&1`
|
|
109
|
+
result = $?.success? ? :pass : :fail
|
|
110
|
+
File.write("reek_output.txt", output)
|
|
111
|
+
summary = output.lines.reverse.find { |l| l =~ /total warnings?/ }&.strip || result.to_s
|
|
112
|
+
puts "Reek: #{summary} (see reek_output.txt)"
|
|
113
|
+
result
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
desc "Check spelling with typos"
|
|
117
|
+
def typos_check
|
|
118
|
+
return skip_typos unless command_available?("typos")
|
|
119
|
+
|
|
120
|
+
output = `typos 2>&1`
|
|
121
|
+
result = $?.success? ? :pass : :fail
|
|
122
|
+
File.write("typos_output.txt", output)
|
|
123
|
+
count = output.lines.count { |l| l.start_with?("error:") }
|
|
124
|
+
summary = result == :pass ? "no typos found" : "#{count} typo(s) found"
|
|
125
|
+
puts "Typos: #{summary} (see typos_output.txt)"
|
|
126
|
+
result
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
desc "Auto-correct typos"
|
|
130
|
+
def typos_fix
|
|
131
|
+
return skip_typos unless command_available?("typos")
|
|
132
|
+
|
|
133
|
+
sh "typos -w"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
desc "Check ERB templates for style and safety issues with ERBLint"
|
|
137
|
+
def erb_lint_check
|
|
138
|
+
return skip_erb_lint if Dir.glob("**/*.erb").empty?
|
|
139
|
+
|
|
140
|
+
output = `bundle exec erb_lint --lint-all --enable-all-linters 2>&1`
|
|
141
|
+
result = $?.success? ? :pass : :fail
|
|
142
|
+
File.write("erb_lint_output.txt", output)
|
|
143
|
+
summary = output.lines.reverse.find { |l| l =~ /error\(s\)|no errors were found/i }&.strip || result.to_s
|
|
144
|
+
puts "ERBLint: #{summary} (see erb_lint_output.txt)"
|
|
145
|
+
result
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
desc "Check for performance suggestions with Fasterer"
|
|
149
|
+
def fasterer_check
|
|
150
|
+
output = `bundle exec fasterer lib 2>&1`.gsub(/\e\[\d+m/, "")
|
|
151
|
+
result = $?.success? ? :pass : :warn
|
|
152
|
+
File.write("fasterer_output.txt", output)
|
|
153
|
+
summary = output.lines.find { |l| l =~ /files? inspected/ }&.strip || result.to_s
|
|
154
|
+
puts "Fasterer: #{summary} (see fasterer_output.txt)"
|
|
155
|
+
result
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
desc "Check Gemfile.lock for known vulnerabilities with bundler-audit"
|
|
159
|
+
def bundler_audit_check
|
|
160
|
+
output = `bundle exec bundle-audit check --update 2>&1`
|
|
161
|
+
result = $?.success? ? :pass : :fail
|
|
162
|
+
File.write("bundler_audit_output.txt", output)
|
|
163
|
+
summary = output.lines.reverse.find { |l| l =~ /vulnerabilit/i }&.strip || result.to_s
|
|
164
|
+
puts "Bundler Audit: #{summary} (see bundler_audit_output.txt)"
|
|
165
|
+
result
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
desc "Check architecture boundaries with ArchSpec"
|
|
169
|
+
def archspec_check
|
|
170
|
+
return skip_archspec unless command_available?("archspec") && File.exist?("Archspec.rb")
|
|
171
|
+
|
|
172
|
+
output = `archspec check 2>&1`
|
|
173
|
+
result = $?.success? ? :pass : :fail
|
|
174
|
+
File.write("archspec_output.txt", output)
|
|
175
|
+
summary = output.lines.reverse.find { |l| l =~ /architecture violations? found|ArchSpec passed/i }&.strip ||
|
|
176
|
+
result.to_s
|
|
177
|
+
puts "ArchSpec: #{summary} (see archspec_output.txt)"
|
|
178
|
+
result
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Curated display labels for the *_check tasks defined in this file. A
|
|
182
|
+
# *_check task from elsewhere (e.g. quality_rails.loki's brakeman_check,
|
|
183
|
+
# loaded automatically once Rails is defined) isn't listed here — it just
|
|
184
|
+
# falls back to a titleized version of its own name in quality_gate_label.
|
|
185
|
+
QUALITY_GATE_LABELS = {
|
|
186
|
+
"test_check" => "Tests + Coverage",
|
|
187
|
+
"rubocop_check" => "RuboCop",
|
|
188
|
+
"flog_check" => "Flog Complexity",
|
|
189
|
+
"flay_check" => "Flay Duplication",
|
|
190
|
+
"reek_check" => "Reek Smells",
|
|
191
|
+
"erb_lint_check" => "ERBLint",
|
|
192
|
+
"archspec_check" => "ArchSpec",
|
|
193
|
+
}.freeze
|
|
194
|
+
|
|
195
|
+
no_commands do
|
|
196
|
+
# true if +name+ resolves to an executable file somewhere on PATH.
|
|
197
|
+
def command_available?(name)
|
|
198
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).any? do |dir|
|
|
199
|
+
File.executable?(File.join(dir, name))
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def quality_gate_label(check_name)
|
|
204
|
+
QUALITY_GATE_LABELS.fetch(check_name) do
|
|
205
|
+
check_name.delete_suffix("_check").split("_").map(&:capitalize).join(" ")
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def skip_typos
|
|
210
|
+
puts "Typos: SKIPPED — `typos` command not found. Install it with `brew install typos-cli`."
|
|
211
|
+
:skip
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def skip_erb_lint
|
|
215
|
+
puts "ERBLint: SKIPPED — no .erb files found."
|
|
216
|
+
:skip
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def skip_archspec
|
|
220
|
+
reason = command_available?("archspec") ? "no Archspec.rb found" : "`archspec` command not found"
|
|
221
|
+
puts "ArchSpec: SKIPPED — #{reason}. Install it with `gem install archspec` " \
|
|
222
|
+
"and run `archspec init` to generate one."
|
|
223
|
+
:skip
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Status badges. Only :fail is blocking (aborts the quality task);
|
|
227
|
+
# :skip (a required tool isn't installed) and :warn (the check ran but
|
|
228
|
+
# has non-blocking suggestions, e.g. a performance-hint gate) are not.
|
|
229
|
+
STATUS_BADGES = {
|
|
230
|
+
pass: ["PASS", 32], # green
|
|
231
|
+
fail: ["FAIL", 31], # red
|
|
232
|
+
warn: ["WARN", 33], # yellow
|
|
233
|
+
skip: ["SKIP", 36], # cyan
|
|
234
|
+
}.freeze
|
|
235
|
+
|
|
236
|
+
# Colorized status badges plus a pass/fail/warn/skip tally; aborts only
|
|
237
|
+
# if any gate failed — skip and warn are informational, not blocking.
|
|
238
|
+
def print_quality_summary(results)
|
|
239
|
+
colorize_badge = ->(code, text) { "\e[#{code}m#{text}\e[0m" }
|
|
240
|
+
width = results.keys.map(&:length).max
|
|
241
|
+
|
|
242
|
+
puts "\n#{"=" * 60}"
|
|
243
|
+
puts "Quality Gate Summary"
|
|
244
|
+
puts "=" * 60
|
|
245
|
+
results.each do |label, status|
|
|
246
|
+
text, code = STATUS_BADGES.fetch(status)
|
|
247
|
+
puts " [#{colorize_badge.call(code, text)}] #{label.ljust(width)}"
|
|
248
|
+
end
|
|
249
|
+
puts "-" * 60
|
|
250
|
+
|
|
251
|
+
counts = results.values.tally
|
|
252
|
+
failed = counts[:fail] || 0
|
|
253
|
+
tally = STATUS_BADGES.keys.filter_map { |s| "#{counts[s]} #{s}" if counts[s]&.positive? }.join(", ")
|
|
254
|
+
puts " #{colorize_badge.call(failed.zero? ? 32 : 31, tally)}"
|
|
255
|
+
puts "=" * 60
|
|
256
|
+
|
|
257
|
+
abort "\n#{colorize_badge.call(31, "Quality gate failed.")}" unless failed.zero?
|
|
258
|
+
puts "\n#{colorize_badge.call(32, "All quality gates passed.")}"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
data/quality_rails.loki
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# Rails-specific quality gate tasks — imported by .loki only when the Rails
|
|
3
|
+
# constant is defined.
|
|
4
|
+
#
|
|
5
|
+
# `quality` (in quality.loki) discovers every *_check task at run time by
|
|
6
|
+
# introspecting Tasks.all_commands, rather than a fixed depends_on list — so
|
|
7
|
+
# any *_check task defined here is automatically picked up by `asgard
|
|
8
|
+
# quality` too, with no need to redeclare or extend anything.
|
|
9
|
+
#
|
|
10
|
+
# Note on rubocop-rails: it's a RuboCop plugin, not a standalone CLI, so it
|
|
11
|
+
# has no *_check task of its own here. Add the gem to the app's Gemfile and
|
|
12
|
+
# `require: rubocop-rails` in its .rubocop.yml — the existing rubocop_check
|
|
13
|
+
# (quality.loki) picks up the added Rails cops automatically.
|
|
14
|
+
|
|
15
|
+
class Tasks
|
|
16
|
+
desc "Check for Rails security vulnerabilities with Brakeman"
|
|
17
|
+
def brakeman_check
|
|
18
|
+
output = `bundle exec brakeman -q 2>&1`
|
|
19
|
+
result = $?.success? ? :pass : :fail
|
|
20
|
+
File.write("brakeman_output.txt", output)
|
|
21
|
+
summary = output.lines.reverse.find { |l| l =~ /warnings? found/i }&.strip || result.to_s
|
|
22
|
+
puts "Brakeman: #{summary} (see brakeman_output.txt)"
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
desc "Check for Rails anti-patterns with RailsBestPractices"
|
|
27
|
+
def rails_best_practices_check
|
|
28
|
+
output = `bundle exec rails_best_practices --silent --without-color . 2>&1`
|
|
29
|
+
result = $?.success? ? :pass : :fail
|
|
30
|
+
File.write("rails_best_practices_output.txt", output)
|
|
31
|
+
summary = output.lines.reverse.find { |l| l =~ /warning/i }&.strip || result.to_s
|
|
32
|
+
puts "RailsBestPractices: #{summary} (see rails_best_practices_output.txt)"
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
desc "Check for database schema issues with ActiveRecordDoctor"
|
|
37
|
+
def active_record_doctor_check
|
|
38
|
+
output = `bundle exec rake active_record_doctor 2>&1`
|
|
39
|
+
result = $?.success? ? :pass : :fail
|
|
40
|
+
File.write("active_record_doctor_output.txt", output)
|
|
41
|
+
issue_count = output.lines.count { |l| l.strip != "" }
|
|
42
|
+
summary = result == :pass ? "no issues found" : "#{issue_count} issue line(s) found"
|
|
43
|
+
puts "ActiveRecordDoctor: #{summary} (see active_record_doctor_output.txt)"
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
end
|
data/xyzzy.loki
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# example of an overwritten task detected by (--doctor) — imported by .loki
|
|
3
|
+
|
|
4
|
+
class Tasks
|
|
5
|
+
desc "Push the current branch to its remote"
|
|
6
|
+
def xyzzy = puts <<~MAGIC
|
|
7
|
+
You are at a fork in the yellow brick road to AI.
|
|
8
|
+
To the left is python, the favorite programming language of snake charmers.
|
|
9
|
+
To the right is Ruby, the best computer programming language of all time.
|
|
10
|
+
Which way do you go?
|
|
11
|
+
MAGIC
|
|
12
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: asgard
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dewayne VanHoozer
|
|
@@ -23,20 +23,6 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '1.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: dagwood
|
|
28
|
-
requirement: !ruby/object:Gem::Requirement
|
|
29
|
-
requirements:
|
|
30
|
-
- - "~>"
|
|
31
|
-
- !ruby/object:Gem::Version
|
|
32
|
-
version: '1.0'
|
|
33
|
-
type: :runtime
|
|
34
|
-
prerelease: false
|
|
35
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
-
requirements:
|
|
37
|
-
- - "~>"
|
|
38
|
-
- !ruby/object:Gem::Version
|
|
39
|
-
version: '1.0'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
27
|
name: dotenv
|
|
42
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -65,13 +51,14 @@ files:
|
|
|
65
51
|
- ".envrc"
|
|
66
52
|
- ".github/workflows/deploy-github-pages.yml"
|
|
67
53
|
- ".loki"
|
|
54
|
+
- ".reek.yml"
|
|
68
55
|
- ".rubocop.yml"
|
|
56
|
+
- Archspec.rb
|
|
69
57
|
- CHANGELOG.md
|
|
70
58
|
- CLAUDE.md
|
|
71
59
|
- COMMITS.md
|
|
72
60
|
- LICENSE.txt
|
|
73
61
|
- README.md
|
|
74
|
-
- Rakefile
|
|
75
62
|
- bin/asgard
|
|
76
63
|
- bin/console
|
|
77
64
|
- bin/setup
|
|
@@ -93,6 +80,7 @@ files:
|
|
|
93
80
|
- docs/variables.md
|
|
94
81
|
- examples/.env
|
|
95
82
|
- examples/.loki
|
|
83
|
+
- examples/bad.loki
|
|
96
84
|
- examples/concurrent.loki
|
|
97
85
|
- examples/db_subcommands.loki
|
|
98
86
|
- examples/env_usage.loki
|
|
@@ -101,14 +89,26 @@ files:
|
|
|
101
89
|
- examples/subdir/.loki
|
|
102
90
|
- examples/subdir/import_demo.loki
|
|
103
91
|
- examples/subdir/import_up_demo.loki
|
|
92
|
+
- gem_tasks.loki
|
|
93
|
+
- git.loki
|
|
104
94
|
- lib/asgard.rb
|
|
105
95
|
- lib/asgard/base.rb
|
|
96
|
+
- lib/asgard/base/dependency_graph.rb
|
|
97
|
+
- lib/asgard/base/dispatch.rb
|
|
98
|
+
- lib/asgard/base/registry.rb
|
|
99
|
+
- lib/asgard/base/task_dsl.rb
|
|
100
|
+
- lib/asgard/doctor.rb
|
|
101
|
+
- lib/asgard/doctor/report.rb
|
|
102
|
+
- lib/asgard/doctor/task_sections.rb
|
|
106
103
|
- lib/asgard/kernel_methods.rb
|
|
107
104
|
- lib/asgard/shell.rb
|
|
108
105
|
- lib/asgard/tasks.rb
|
|
109
106
|
- lib/asgard/version.rb
|
|
110
107
|
- mkdocs.yml
|
|
108
|
+
- quality.loki
|
|
109
|
+
- quality_rails.loki
|
|
111
110
|
- sig/asgard.rbs
|
|
111
|
+
- xyzzy.loki
|
|
112
112
|
homepage: https://github.com/madbomber/asgard
|
|
113
113
|
licenses:
|
|
114
114
|
- MIT
|
|
@@ -131,7 +131,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
131
131
|
- !ruby/object:Gem::Version
|
|
132
132
|
version: '0'
|
|
133
133
|
requirements: []
|
|
134
|
-
rubygems_version: 4.0.
|
|
134
|
+
rubygems_version: 4.0.19
|
|
135
135
|
specification_version: 4
|
|
136
136
|
summary: A powerful Ruby-based task runner
|
|
137
137
|
test_files: []
|
data/Rakefile
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "bundler/gem_tasks"
|
|
4
|
-
require "minitest/test_task"
|
|
5
|
-
|
|
6
|
-
SIMPLECOV_PRELUDE = <<~RUBY
|
|
7
|
-
require "simplecov"
|
|
8
|
-
SimpleCov.start do
|
|
9
|
-
add_filter "/test/"
|
|
10
|
-
minimum_coverage 95
|
|
11
|
-
end
|
|
12
|
-
RUBY
|
|
13
|
-
|
|
14
|
-
Minitest::TestTask.create do |t|
|
|
15
|
-
t.test_prelude = SIMPLECOV_PRELUDE
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
task default: :test
|
|
19
|
-
|
|
20
|
-
RUBOCOP_ENV = { "RUBOCOP_CACHE_ROOT" => "tmp/rubocop_cache" }.freeze
|
|
21
|
-
|
|
22
|
-
desc "Check code style with RuboCop"
|
|
23
|
-
task :rubocop do
|
|
24
|
-
sh RUBOCOP_ENV, "bundle exec rubocop"
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
desc "Auto-correct RuboCop offenses"
|
|
28
|
-
task :rubocop_fix do
|
|
29
|
-
sh RUBOCOP_ENV, "bundle exec rubocop -a"
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
desc "Check code complexity with Flog (warn >=20, fail >=50)"
|
|
33
|
-
task :flog_check do
|
|
34
|
-
require "flog"
|
|
35
|
-
|
|
36
|
-
# Target to work toward; methods above this are warned but don't fail the gate.
|
|
37
|
-
METHOD_WARN = 20.0
|
|
38
|
-
# Current baseline floor — established from first run. Reduce incrementally.
|
|
39
|
-
METHOD_FAIL = 50.0
|
|
40
|
-
|
|
41
|
-
flogger = Flog.new(all: true)
|
|
42
|
-
flogger.flog(*Dir.glob("lib/**/*.rb"))
|
|
43
|
-
|
|
44
|
-
warnings = []
|
|
45
|
-
failures = []
|
|
46
|
-
|
|
47
|
-
flogger.each_by_score do |method, score|
|
|
48
|
-
next if method.end_with?("#none")
|
|
49
|
-
if score > METHOD_FAIL
|
|
50
|
-
failures << "#{"%.1f" % score}: #{method}"
|
|
51
|
-
elsif score > METHOD_WARN
|
|
52
|
-
warnings << "#{"%.1f" % score}: #{method}"
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
unless warnings.empty?
|
|
57
|
-
puts "\nFlog warnings (#{METHOD_WARN}–#{METHOD_FAIL}) — target for future refactoring:"
|
|
58
|
-
warnings.each { |v| puts " #{v}" }
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
if failures.empty?
|
|
62
|
-
puts "\nFlog: no methods exceed the failure threshold (>=#{METHOD_FAIL})"
|
|
63
|
-
else
|
|
64
|
-
puts "\nFlog failures (>=#{METHOD_FAIL}) — must be refactored:"
|
|
65
|
-
failures.each { |v| puts " #{v}" }
|
|
66
|
-
$stdout.flush
|
|
67
|
-
abort "\nFlog quality gate failed: #{failures.size} method(s) exceed #{METHOD_FAIL}"
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
desc "Run all quality checks: tests (with coverage), RuboCop, and Flog"
|
|
72
|
-
task :quality do
|
|
73
|
-
results = {}
|
|
74
|
-
|
|
75
|
-
puts "\n#{"=" * 60}"
|
|
76
|
-
puts "Quality Gate: Tests + Coverage"
|
|
77
|
-
puts "=" * 60
|
|
78
|
-
results[:tests] = system("bundle exec rake test") ? :pass : :fail
|
|
79
|
-
|
|
80
|
-
puts "\n#{"=" * 60}"
|
|
81
|
-
puts "Quality Gate: RuboCop"
|
|
82
|
-
puts "=" * 60
|
|
83
|
-
results[:rubocop] = system(RUBOCOP_ENV, "bundle exec rubocop") ? :pass : :fail
|
|
84
|
-
|
|
85
|
-
puts "\n#{"=" * 60}"
|
|
86
|
-
puts "Quality Gate: Flog Complexity"
|
|
87
|
-
puts "=" * 60
|
|
88
|
-
results[:flog] = system("bundle exec rake flog_check") ? :pass : :fail
|
|
89
|
-
|
|
90
|
-
puts "\n#{"=" * 60}"
|
|
91
|
-
puts "Quality Summary"
|
|
92
|
-
puts "=" * 60
|
|
93
|
-
results.each do |gate, status|
|
|
94
|
-
icon = status == :pass ? "PASS" : "FAIL"
|
|
95
|
-
puts " [#{icon}] #{gate}"
|
|
96
|
-
end
|
|
97
|
-
puts "=" * 60
|
|
98
|
-
|
|
99
|
-
abort "\nQuality gate failed" if results.values.any?(:fail)
|
|
100
|
-
puts "\nAll quality gates passed."
|
|
101
|
-
end
|