deployangel 0.1.34 → 0.1.35
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/CHANGELOG.md +7 -0
- data/README.md +4 -2
- data/lib/deployangel/cli.rb +15 -1
- data/lib/deployangel/exerciser.rb +17 -2
- data/lib/deployangel/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 275f765f13eee93ddc36007727d1f2278b343a524562c062eeab1a258f8abb16
|
|
4
|
+
data.tar.gz: f167ad720f710b5cf3a527632795972a5dbbe972309bbbf470a937425d0282e7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cdd30481a4d28645ef05f5d570f253d73029258581743045677e5a7e1c19a395b28082656aa44f588eac2c8be074b01a93240102143a822d166fa250325efd93
|
|
7
|
+
data.tar.gz: 5190303791d16fb83affbed23cd01e5b20c1b28a245f94824ee9f01670ec454a9b6c9696d632cabc3ce7c0990da8d0809adb14b7c3eddeda7aa8a5ec077f7f54
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.35 (2026-11-01)
|
|
4
|
+
|
|
5
|
+
- `deployangel exercise` prints each route's status codes (`404 ×1`)
|
|
6
|
+
instead of their class (`1 4xx`). When every page answers 404, 405, or
|
|
7
|
+
410, as when `--url` is the app's marketing site rather than its own
|
|
8
|
+
host, it says so, records nothing on the release, and exits 5.
|
|
9
|
+
|
|
3
10
|
## 0.1.34 (2026-11-01)
|
|
4
11
|
|
|
5
12
|
- `deployangel verify` prints a failed release's next steps: the last
|
data/README.md
CHANGED
|
@@ -603,8 +603,10 @@ requests, about 5 a second, as `DeployAngel-Exercise`), then records what it
|
|
|
603
603
|
sent on the release, where the page lists it under "Exercised from your
|
|
604
604
|
side". It skips and names routes that change data or need a path
|
|
605
605
|
parameter, and stops requesting a page after its first 404, sending its
|
|
606
|
-
share to the pages that answered.
|
|
607
|
-
|
|
606
|
+
share to the pages that answered. It prints each route's status codes. If
|
|
607
|
+
every page answers 404, 405, or 410, it records nothing and exits 5, since
|
|
608
|
+
the URL is probably not the app's own host (a marketing site, say). A "CLI
|
|
609
|
+
& coding agents" token can run it. `--dry-run` shows what it would send.
|
|
608
610
|
|
|
609
611
|
### Suggested instructions for your coding agent
|
|
610
612
|
|
data/lib/deployangel/cli.rb
CHANGED
|
@@ -319,9 +319,18 @@ module DeployAngel
|
|
|
319
319
|
|
|
320
320
|
result = exerciser.run(plan)
|
|
321
321
|
@stdout.puts("Sent #{result.sent} requests to #{options[:url]}:",
|
|
322
|
-
*result.routes.map { |r| " #{r["key"]} ×#{r["requests"]}: #{r["
|
|
322
|
+
*result.routes.map { |r| " #{r["key"]} ×#{r["requests"]}: #{status_codes(result.codes[r["key"]])}" })
|
|
323
323
|
@stdout.puts("Skipped:", *result.skipped.map { |s| " #{s["key"]} (#{s["reason"]})" }) if result.skipped.any?
|
|
324
324
|
@stdout.puts("Stopped early: #{options[:url]} isn't answering.") if result.unreachable
|
|
325
|
+
# Requests that reached no page aren't worth recording: the app
|
|
326
|
+
# likely never saw them.
|
|
327
|
+
if result.routes.any? && result.nothing_served? && !result.unreachable
|
|
328
|
+
@stdout.puts("", "No page answered at #{options[:url]}: every route said it doesn't exist. Is that the app's own host, " \
|
|
329
|
+
"and not a marketing site or a different domain? Pass the URL where the app's pages load.")
|
|
330
|
+
@stdout.puts("#{result.unsent} more requests weren't sent, since no route answered.") if result.unsent.positive?
|
|
331
|
+
@stdout.puts("Nothing was recorded on the release.")
|
|
332
|
+
return USAGE_ERROR
|
|
333
|
+
end
|
|
325
334
|
if result.routes.any?
|
|
326
335
|
client.record_exercise(document.dig("deployment", "id"), routes: result.routes, skipped: result.skipped, ran_at: @clock.call.utc.iso8601)
|
|
327
336
|
@stdout.puts("Recorded on the release. Wait for the verdict with: deployangel verify --wait")
|
|
@@ -329,6 +338,11 @@ module DeployAngel
|
|
|
329
338
|
result.unreachable ? USAGE_ERROR : 0
|
|
330
339
|
end
|
|
331
340
|
|
|
341
|
+
# "200 ×4, 500 ×1", in the order they came.
|
|
342
|
+
def status_codes(codes)
|
|
343
|
+
codes.map { |code, n| "#{code || "no answer"} ×#{n}" }.join(", ")
|
|
344
|
+
end
|
|
345
|
+
|
|
332
346
|
def exception
|
|
333
347
|
fingerprint = @argv.shift or return usage_error("exception needs a FINGERPRINT")
|
|
334
348
|
format = parse(format: nil) { |o, opts| o.on("--format=FORMAT", %w[text json]) { |v| opts[:format] = v } }[:format]
|
|
@@ -23,7 +23,18 @@ module DeployAngel
|
|
|
23
23
|
PARAMETER = /\/[:*{<]/
|
|
24
24
|
|
|
25
25
|
Target = Struct.new(:key, :path, :count)
|
|
26
|
-
|
|
26
|
+
# codes: each route's exact status codes, nil for no answer, for
|
|
27
|
+
# display; routes carries status classes, which is what's recorded.
|
|
28
|
+
# unsent: requests the plan wanted that went nowhere, since no route
|
|
29
|
+
# answered.
|
|
30
|
+
Result = Struct.new(:routes, :skipped, :sent, :unreachable, :codes, :unsent, keyword_init: true) do
|
|
31
|
+
# Every answer said the page isn't served. Usually the wrong host,
|
|
32
|
+
# like the app's marketing site.
|
|
33
|
+
def nothing_served?
|
|
34
|
+
answers = codes.values.flat_map(&:keys)
|
|
35
|
+
answers.any? && answers.all? { |code| NOT_SERVED.include?(code) }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
27
38
|
|
|
28
39
|
def initialize(base_url:, max_requests: MAX_REQUESTS, sleeper: ->(seconds) { sleep(seconds) }, requester: nil)
|
|
29
40
|
@base = URI(base_url)
|
|
@@ -59,6 +70,7 @@ module DeployAngel
|
|
|
59
70
|
def run(exercise_plan)
|
|
60
71
|
targets, skipped = plan(exercise_plan)
|
|
61
72
|
@statuses = Hash.new { |hash, key| hash[key] = Hash.new(0) }
|
|
73
|
+
@codes = Hash.new { |hash, key| hash[key] = Hash.new(0) }
|
|
62
74
|
@sent = 0
|
|
63
75
|
@errors_in_a_row = 0
|
|
64
76
|
leftover = 0
|
|
@@ -76,12 +88,14 @@ module DeployAngel
|
|
|
76
88
|
answered << target if @statuses[target.key].keys.intersect?(%w[2xx 3xx])
|
|
77
89
|
end
|
|
78
90
|
leftover.times { |i| send_request(answered[i % answered.size]) unless unreachable? } if answered.any?
|
|
91
|
+
unsent = answered.empty? ? leftover : 0
|
|
79
92
|
|
|
80
93
|
routes = targets.filter_map do |target|
|
|
81
94
|
statuses = @statuses.fetch(target.key, nil) or next
|
|
82
95
|
{ "key" => target.key, "requests" => statuses.values.sum, "statuses" => statuses.to_h }
|
|
83
96
|
end
|
|
84
|
-
Result.new(routes: routes, skipped: skipped, sent: @sent, unreachable: unreachable
|
|
97
|
+
Result.new(routes: routes, skipped: skipped, sent: @sent, unreachable: unreachable?,
|
|
98
|
+
codes: routes.to_h { |route| [ route["key"], @codes[route["key"]].to_h ] }, unsent: unsent)
|
|
85
99
|
end
|
|
86
100
|
|
|
87
101
|
def url_for(path)
|
|
@@ -98,6 +112,7 @@ module DeployAngel
|
|
|
98
112
|
@sent += 1
|
|
99
113
|
kind = status ? "#{status / 100}xx" : "error"
|
|
100
114
|
@statuses[target.key][kind] += 1
|
|
115
|
+
@codes[target.key][status] += 1
|
|
101
116
|
@errors_in_a_row = kind == "error" ? @errors_in_a_row + 1 : 0
|
|
102
117
|
status
|
|
103
118
|
end
|
data/lib/deployangel/version.rb
CHANGED