deployangel 0.1.27 → 0.1.29

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 480a4d0a0e8ff4206659a86d40ea0fbfb2c5cc20f55e205aa5a724914add1acf
4
- data.tar.gz: bdd81c91faac1642d5f78633ab48bd2efb80e15d6a8b3022d4a0d8e18c967f4f
3
+ metadata.gz: e62d94a1e10efd0431f1cfad4f7aad29f954481919a609f8112e5f517291eeb1
4
+ data.tar.gz: 408bcc12ae37d4f93e74e5e31ad6a8b921b30481cedf81f84055c07c62677af0
5
5
  SHA512:
6
- metadata.gz: e669f7ad136b1328c7f672120dc00b7f5afb20a09a6ca7b27a37c25e26709f678aec6c5fb24dcc1de68c2ffd04a73e1687d2132f09c94ea95cabf72d4731c597
7
- data.tar.gz: 88e02d9c250287ac051b233bada6a7469507860aa62cc03390dee8b566770eba38c79431b0e882778b9351a9ca59d03c68b26c8d9cd188c390ee65ad0a4093e8
6
+ metadata.gz: e26a0379ee22236add3b1742a3ef174ebd3f39def909d3e230b4fadce49b1f2a3f62daa41c2aa02e05e5c12e59a78b9d6c94b7436bca9b986c56b20053f49783
7
+ data.tar.gz: 9d7f93900e16f63f1ba593063a31b0d03eb3f218877c65ea6c5a9b13153bd9a3520c8e5555370f8d94e53d89741f0a709143483b8ffa196e0e2e23f9a42a4b31
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.29 (2026-10-16)
4
+
5
+ - Reports stuck work, so DeployAngel can fail a release in which a job hangs
6
+ or the queue keeps growing while every job still succeeds. Each minute's
7
+ payload says how many jobs are still running and how long the oldest of
8
+ each class has been running (ActiveJob on any adapter, and native Sidekiq
9
+ jobs). Sidekiq and Solid Queue worker processes also read the queue backlog,
10
+ the jobs ready to run and waiting, once a minute from the reporter thread:
11
+ one pipelined Redis call for Sidekiq, one `COUNT` of
12
+ `solid_queue_ready_executions` for Solid Queue. Web processes don't read
13
+ it. Needs a DeployAngel cloud that understands the new fields; older ones
14
+ ignore them.
15
+
16
+ ## 0.1.28 (2026-10-12)
17
+
18
+ - Clearer setup for coding agents, from a first-day rehearsal: `setup` says
19
+ the running app reads its token only from `DEPLOYANGEL_TOKEN` and points to
20
+ the per-host steps instead of printing Fly and Heroku examples; `install
21
+ agents` says the saved token is used here once `setup` has run; `doctor`'s
22
+ fix for an agent that hasn't reported says how to pass a saved app token;
23
+ and the help says `release` defaults to the current commit and when
24
+ `--format` defaults to json.
25
+
3
26
  ## 0.1.27 (2026-10-12)
4
27
 
5
28
  - The MCP server's instructions, and the ones `deployangel install agents`
data/README.md CHANGED
@@ -88,6 +88,10 @@ the dashboard says so.
88
88
  queue latency, per job class. Works with every ActiveJob adapter (Solid
89
89
  Queue, Sidekiq, GoodJob, ...) and with native Sidekiq jobs through a server
90
90
  middleware. Failures that `retry_on` or `discard_on` handle still count.
91
+ - Jobs still running at the end of each minute, and how long the oldest of
92
+ each class has been running, so a job that hangs is noticed. Sidekiq and
93
+ Solid Queue worker processes also send the queue backlog (how many jobs are
94
+ waiting), read once a minute off the request path.
91
95
  - Release identity, runtime versions, and a per-process instance ID.
92
96
 
93
97
  - Exceptions: a stable fingerprint, the exception class, the first line of the
@@ -86,6 +86,25 @@ module DeployAngel
86
86
  warn_once(:record_job, "DeployAngel failed to record a job: #{e.class}: #{e.message}")
87
87
  end
88
88
 
89
+ # Marks a job as running until #job_finished, so one that hangs is still
90
+ # reported. Returns the token to pass to it.
91
+ def job_started(job_class)
92
+ return unless @active
93
+
94
+ after_fork! if Process.pid != @pid
95
+ start_reporter
96
+ @running_jobs.start(job_class)
97
+ rescue StandardError => e
98
+ warn_once(:job_started, "DeployAngel failed to record a running job: #{e.class}: #{e.message}")
99
+ nil
100
+ end
101
+
102
+ def job_finished(token)
103
+ @running_jobs.finish(token) if token
104
+ rescue StandardError => e
105
+ warn_once(:job_finished, "DeployAngel failed to record a finished job: #{e.class}: #{e.message}")
106
+ end
107
+
89
108
  def record_discard(job_class:)
90
109
  return unless @active
91
110
 
@@ -135,7 +154,9 @@ module DeployAngel
135
154
  def flush(include_current: false)
136
155
  return 0 unless @active
137
156
 
138
- @aggregator.drain(include_current: include_current, max_periods: config.max_queued_payloads).each do |period|
157
+ periods = @aggregator.drain(include_current: include_current, max_periods: config.max_queued_payloads)
158
+ add_stuck_work(periods.last, include_current) if periods.any?
159
+ periods.each do |period|
139
160
  @buffer.push(Transport.encode(Protocol.telemetry(period, instance: @instance, release: release,
140
161
  runtime: @runtime, capabilities: capabilities)))
141
162
  end
@@ -198,10 +219,24 @@ module DeployAngel
198
219
  end
199
220
 
200
221
  private
222
+ # What's still running, on the latest period only: as of its end, or
223
+ # now for the in-progress one at shutdown. The backlog is read here, in
224
+ # the reporter thread, but not at shutdown, which has seconds to send.
225
+ def add_stuck_work(period, include_current)
226
+ ended_at = [ period.started_at + Aggregator::PERIOD_SECONDS, @clock.call ].min
227
+ @running_jobs.snapshot(ended_at).each do |job_class, (count, oldest_ms)|
228
+ period.record_running(job_class, count, oldest_ms, config.max_routes)
229
+ end
230
+ period.queue_backlog = QueueBacklog.read unless include_current
231
+ rescue StandardError => e
232
+ warn_once(:stuck_work, "DeployAngel failed to read running jobs: #{e.class}: #{e.message}")
233
+ end
234
+
201
235
  def reset_process_state
202
236
  @pid = Process.pid
203
237
  @instance = Instance.new(env: @env, now: @clock.call)
204
238
  @aggregator = Aggregator.new(max_routes: config.max_routes, clock: @clock)
239
+ @running_jobs = RunningJobs.new(clock: @clock)
205
240
  @buffer = Buffer.new(config.max_queued_payloads)
206
241
  @paused_until = nil
207
242
  @metadata_sent = false
@@ -49,7 +49,8 @@ module DeployAngel
49
49
  into a host's or CI's secrets
50
50
  mcp Run the MCP server over stdio (for coding agents)
51
51
 
52
- With no target, verify, plan, and check use the current git HEAD commit.
52
+ With no target, release, verify, plan, and check use the current git HEAD commit.
53
+ --format defaults to text in a terminal and json otherwise, such as in a pipe or an agent.
53
54
  In GitHub Actions, GitLab CI, CircleCI, and Buildkite, release fills in the
54
55
  commit, a build label (e.g. run-123), and a link to the run automatically.
55
56
  In a Kamal hook, it registers Kamal's release (KAMAL_VERSION).
@@ -123,10 +124,19 @@ module DeployAngel
123
124
  #{AGENTS_END}
124
125
  MARKDOWN
125
126
 
127
+ # After `deployangel setup`, the MCP server and CLI here use its saved token.
128
+ AGENTS_SAVED_TOKEN_HELP = <<~HELP.chomp
129
+ Here, the MCP server and CLI use the token `deployangel setup` saved in .deployangel/.
130
+ Teammates redeem their own setup code, or set DEPLOYANGEL_API_TOKEN to a
131
+ "CLI & coding agents" token from the app's Settings, for example in an .envrc
132
+ with direnv. These files hold no token and are meant to be committed.
133
+ Codex reads .codex/config.toml only in projects you've marked as trusted.
134
+ HELP
126
135
  AGENTS_TOKEN_HELP = <<~HELP.chomp
127
136
  The MCP server and CLI need DEPLOYANGEL_API_TOKEN, a "CLI & coding agents" token
128
137
  from the app's Settings, in the environment your agent runs in (for example in
129
- an .envrc with direnv). Never put it in these files: they're meant to be committed.
138
+ an .envrc with direnv), or a token saved here by `deployangel setup`.
139
+ Never put it in these files: they're meant to be committed.
130
140
  Codex reads .codex/config.toml only in projects you've marked as trusted.
131
141
  HELP
132
142
 
@@ -414,7 +424,7 @@ module DeployAngel
414
424
  install_codex_mcp(command),
415
425
  *install_agent_instructions
416
426
  ]
417
- @stdout.puts(*lines, "", AGENTS_TOKEN_HELP)
427
+ @stdout.puts(*lines, "", Credentials.find(@root) ? AGENTS_SAVED_TOKEN_HELP : AGENTS_TOKEN_HELP)
418
428
  0
419
429
  end
420
430
 
@@ -621,11 +631,11 @@ module DeployAngel
621
631
  Set up DeployAngel for #{app["name"]} (#{app["environment"]}). Saved its app token and a "CI deploys" token to
622
632
  #{relative}, kept out of git and not shown here. The deployangel command and MCP server here use them.
623
633
 
624
- Give production the app token without printing it, for example:
625
- fly secrets set DEPLOYANGEL_TOKEN=$(#{command_name} token app)
626
- heroku config:set DEPLOYANGEL_TOKEN=$(#{command_name} token app)
627
- and CI the "CI deploys" token:
628
- #{command_name} token api | gh secret set DEPLOYANGEL_API_TOKEN
634
+ The running app reads its token only from DEPLOYANGEL_TOKEN in its environment, so give it the app
635
+ token without printing it: $(#{command_name} token app) in the command that starts it or sets your
636
+ host's secrets, or #{command_name} token app | <a command that reads a secret>. The same goes for CI and
637
+ the "CI deploys" token (#{command_name} token api). Steps for each host:
638
+ https://www.deployangel.com/setup-for-agents.md
629
639
  TEXT
630
640
  0
631
641
  end
@@ -19,9 +19,16 @@ module DeployAngel
19
19
  end
20
20
  end
21
21
 
22
- JobStats = Struct.new(:processed, :failed, :discarded, :duration, :queue_latency) do
22
+ # running and oldest_running_ms: jobs still running as the period is
23
+ # sent, and how long the oldest had been running at its end.
24
+ JobStats = Struct.new(:processed, :failed, :discarded, :duration, :queue_latency, :running, :oldest_running_ms) do
23
25
  def self.empty
24
- new(0, 0, 0, Histogram.new, Histogram.new)
26
+ new(0, 0, 0, Histogram.new, Histogram.new, 0, nil)
27
+ end
28
+
29
+ def add_running(count, oldest_ms)
30
+ self.running += count
31
+ self.oldest_running_ms = [ oldest_running_ms, oldest_ms ].compact.max
25
32
  end
26
33
 
27
34
  def record(failed, discarded, duration_ms, queue_latency_ms)
@@ -54,6 +61,8 @@ module DeployAngel
54
61
  class Period
55
62
  attr_reader :started_at, :requests, :status_counts, :unhandled_exceptions, :histogram, :routes,
56
63
  :jobs, :job_classes, :exceptions, :exceptions_truncated, :checkpoints
64
+ # Jobs waiting in the job backend's queues, read as the period is sent.
65
+ attr_accessor :queue_backlog
57
66
 
58
67
  def initialize(started_at)
59
68
  @started_at = started_at
@@ -97,6 +106,14 @@ module DeployAngel
97
106
  (@job_classes[key] ||= JobStats.empty).record(failed, discarded, duration_ms, queue_latency_ms)
98
107
  end
99
108
 
109
+ # A class with jobs running and none finished is listed too, so a job
110
+ # that hangs is reported.
111
+ def record_running(job_class, count, oldest_ms, max_classes)
112
+ @jobs.add_running(count, oldest_ms)
113
+ key = @job_classes.key?(job_class) || @job_classes.size < max_classes - 1 ? job_class : OTHER_ROUTE
114
+ (@job_classes[key] ||= JobStats.empty).add_running(count, oldest_ms)
115
+ end
116
+
100
117
  def record_discard(job_class, max_classes)
101
118
  @jobs.record_discard
102
119
  key = @job_classes.key?(job_class) || @job_classes.size < max_classes - 1 ? job_class : OTHER_ROUTE
@@ -32,7 +32,7 @@ module DeployAngel
32
32
  end,
33
33
  "exceptions" => period.exceptions.values.map { |e| e.merge("sources" => e["sources"].to_h).compact },
34
34
  "exceptions_truncated" => period.exceptions_truncated,
35
- "jobs" => job_stats(period.jobs),
35
+ "jobs" => job_stats(period.jobs).merge({ "queue_backlog" => period.queue_backlog }.compact),
36
36
  "job_classes" => period.job_classes.map { |key, stats| { "key" => key }.merge(job_stats(stats)) },
37
37
  # http and job are always sent, 0 when none, so the cloud can tell
38
38
  # this agent from one that doesn't say where checkpoints happened.
@@ -42,14 +42,18 @@ module DeployAngel
42
42
  }
43
43
  end
44
44
 
45
+ # running and oldest_running_ms only when jobs were running.
45
46
  def job_stats(stats)
46
- {
47
+ fields = {
47
48
  "processed" => stats.processed,
48
49
  "failed" => stats.failed,
49
50
  "discarded" => stats.discarded,
50
51
  "duration_histogram" => stats.duration.to_protocol,
51
52
  "queue_latency_histogram" => stats.queue_latency.to_protocol
52
53
  }
54
+ return fields unless stats.running.positive?
55
+
56
+ fields.merge("running" => stats.running, "oldest_running_ms" => stats.oldest_running_ms)
53
57
  end
54
58
 
55
59
  def runtime(framework: nil, framework_version: nil)
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeployAngel
4
+ # The jobs this process is running now, so each payload can say how long
5
+ # the oldest of each class has been running: a job that hangs never
6
+ # finishes, so it never shows up in the counts. Starting and finishing is
7
+ # a hash update under a mutex.
8
+ class RunningJobs
9
+ # More than any process runs at once; past it, new jobs go untracked
10
+ # rather than growing memory.
11
+ MAX = 1_000
12
+
13
+ def initialize(clock:)
14
+ @clock = clock
15
+ @jobs = {}.compare_by_identity
16
+ @mutex = Mutex.new
17
+ end
18
+
19
+ # Returns the token to pass to #finish.
20
+ def start(job_class)
21
+ token = Object.new
22
+ @mutex.synchronize { @jobs[token] = [ job_class.to_s, @clock.call ] if @jobs.size < MAX }
23
+ token
24
+ end
25
+
26
+ def finish(token)
27
+ @mutex.synchronize { @jobs.delete(token) }
28
+ end
29
+
30
+ # { job_class => [running, oldest_running_ms] } for jobs that started by
31
+ # at and are still running.
32
+ def snapshot(at)
33
+ @mutex.synchronize do
34
+ @jobs.each_value.with_object({}) do |(job_class, started_at), running|
35
+ next if started_at > at
36
+
37
+ age_ms = ((at - started_at) * 1000).round
38
+ count, oldest = running[job_class]
39
+ running[job_class] = [ count.to_i + 1, [ oldest.to_i, age_ms ].max ]
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "time"
4
4
  require_relative "configuration"
5
+ require_relative "credentials"
5
6
  require_relative "metadata"
6
7
  require_relative "rails/metadata"
7
8
  require_relative "core/release"
@@ -99,8 +100,20 @@ module DeployAngel
99
100
  Check.new(status: :warn, message: "The agent last reported #{time(agent["last_received_at"])}, and nothing since",
100
101
  fix: "Check the app is running, and that DEPLOYANGEL_TOKEN is still set on its processes.")
101
102
  else
102
- Check.new(status: :warn, message: "The agent hasn't reported yet",
103
- fix: "Deploy with DEPLOYANGEL_TOKEN set to the app's agent token (Settings, Tokens: \"Deployed application\").")
103
+ Check.new(status: :warn, message: "The agent hasn't reported yet", fix: token_fix)
104
+ end
105
+ end
106
+
107
+ # The running app reads its token only from DEPLOYANGEL_TOKEN in its
108
+ # environment, never from the file `deployangel setup` saves.
109
+ def token_fix
110
+ if Credentials.find(@root)
111
+ command = File.exist?(File.join(@root, "Gemfile")) ? "bundle exec deployangel" : "deployangel"
112
+ "Start or deploy the app with DEPLOYANGEL_TOKEN in its processes' environment. The app token is saved here, " \
113
+ "so pass it without printing it: $(#{command} token app) in the start command or your host's secrets command."
114
+ else
115
+ "Start or deploy the app with DEPLOYANGEL_TOKEN in its processes' environment, set to its app token " \
116
+ "(\"Deployed application\" in the app's Settings, under API tokens)."
104
117
  end
105
118
  end
106
119
 
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeployAngel
4
+ # Reads how many jobs are ready to run and waiting in the job backend's
5
+ # queues. Only job worker processes read it, once a minute from the
6
+ # reporter thread as they send telemetry, never in a request or job. Every
7
+ # worker sees the same queues, and the cloud keeps the largest reading.
8
+ module QueueBacklog
9
+ module_function
10
+
11
+ # Solid Queue runs each worker in its own process, forked by the
12
+ # supervisor, so it's marked as each one starts.
13
+ def install
14
+ return if @installed || !defined?(::SolidQueue) || !::SolidQueue.respond_to?(:on_worker_start)
15
+
16
+ @installed = true
17
+ ::SolidQueue.on_worker_start { worker!(:solid_queue) }
18
+ rescue StandardError
19
+ nil
20
+ end
21
+
22
+ # Called where the backend says this process works its queues: Sidekiq's
23
+ # server configuration, or a Solid Queue worker starting.
24
+ def worker!(backend)
25
+ @backend = backend
26
+ end
27
+
28
+ def backend
29
+ @backend
30
+ end
31
+
32
+ # Jobs waiting, or nil when this process doesn't work a queue it can read.
33
+ def read
34
+ case @backend
35
+ when :sidekiq then sidekiq
36
+ when :solid_queue then solid_queue
37
+ end
38
+ rescue StandardError, LoadError
39
+ nil
40
+ end
41
+
42
+ # One pipelined call for every queue's length.
43
+ def sidekiq
44
+ require "sidekiq/api"
45
+ ::Sidekiq::Stats.new.enqueued
46
+ end
47
+
48
+ # Ready executions only: scheduled and blocked jobs aren't waiting on a
49
+ # worker. The connection goes straight back to the pool.
50
+ def solid_queue
51
+ model = ::SolidQueue::ReadyExecution
52
+ model.connection_pool.with_connection { model.count }
53
+ end
54
+ end
55
+ end
@@ -12,12 +12,14 @@ module DeployAngel
12
12
  module ActiveJob
13
13
  STATE = :@__deployangel_job_state
14
14
  PREVIOUS_CONTEXT = :@__deployangel_previous_context
15
+ RUNNING = :@__deployangel_running
15
16
 
16
17
  # Marks the fiber as running a job for as long as perform.active_job
17
- # lasts, so checkpoints recorded by the job say so. ActiveSupport calls
18
- # finish from an ensure, so the previous context (an HTTP request, for
19
- # a job performed inline) comes back even when the job raises. It is
20
- # kept on the job, so only a perform that started here restores.
18
+ # lasts, so checkpoints recorded by the job say so, and the job as
19
+ # running, so one that hangs is reported. ActiveSupport calls finish
20
+ # from an ensure, so the previous context (an HTTP request, for a job
21
+ # performed inline) comes back even when the job raises. Both are kept
22
+ # on the job, so only a perform that started here finishes them.
21
23
  module ContextListener
22
24
  module_function
23
25
 
@@ -26,6 +28,7 @@ module DeployAngel
26
28
  return unless job && DeployAngel.recording?
27
29
 
28
30
  job.instance_variable_set(PREVIOUS_CONTEXT, ExecutionContext.enter(ExecutionContext::JOB))
31
+ job.instance_variable_set(RUNNING, DeployAngel.job_started(job.class.name))
29
32
  rescue StandardError
30
33
  nil
31
34
  end
@@ -35,6 +38,7 @@ module DeployAngel
35
38
  return unless job&.instance_variable_defined?(PREVIOUS_CONTEXT)
36
39
 
37
40
  ExecutionContext.restore(job.remove_instance_variable(PREVIOUS_CONTEXT))
41
+ DeployAngel.job_finished(job.remove_instance_variable(RUNNING)) if job.instance_variable_defined?(RUNNING)
38
42
  rescue StandardError
39
43
  nil
40
44
  end
@@ -12,6 +12,7 @@ module DeployAngel
12
12
  initializer "deployangel.jobs" do
13
13
  ActiveSupport.on_load(:active_job) { DeployAngel::Rails::ActiveJob.install }
14
14
  DeployAngel::Sidekiq.install
15
+ DeployAngel::QueueBacklog.install
15
16
  end
16
17
 
17
18
  # Handled reports (Rails.error.handle / Rails.error.report) are shown for
@@ -17,6 +17,7 @@ module DeployAngel
17
17
  started = Process.clock_gettime(Process::CLOCK_MONOTONIC)
18
18
  latency = DeployAngel::Sidekiq.queue_latency_ms(job)
19
19
  previous_context = ExecutionContext.enter(ExecutionContext::JOB)
20
+ running = DeployAngel.job_started(job["class"])
20
21
  begin
21
22
  yield
22
23
  rescue Exception => e # rubocop:disable Lint/RescueException -- recorded, then re-raised untouched
@@ -25,6 +26,7 @@ module DeployAngel
25
26
  raise
26
27
  ensure
27
28
  ExecutionContext.restore(previous_context)
29
+ DeployAngel.job_finished(running)
28
30
  end
29
31
  record(job, started, latency, failed: false)
30
32
  end
@@ -46,6 +48,7 @@ module DeployAngel
46
48
  @installed = true
47
49
  DeployAngel.add_capability("jobs")
48
50
  ::Sidekiq.configure_server do |config|
51
+ QueueBacklog.worker!(:sidekiq)
49
52
  config.server_middleware { |chain| chain.add(ServerMiddleware) }
50
53
  # Jobs that exhausted their retries are discarded.
51
54
  config.death_handlers << ->(job, _error) { record_death(job) } if config.respond_to?(:death_handlers)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeployAngel
4
- VERSION = "0.1.27"
4
+ VERSION = "0.1.29"
5
5
  end
data/lib/deployangel.rb CHANGED
@@ -8,6 +8,7 @@ require_relative "deployangel/core/fingerprint"
8
8
  require_relative "deployangel/core/release"
9
9
  require_relative "deployangel/core/instance"
10
10
  require_relative "deployangel/core/aggregator"
11
+ require_relative "deployangel/core/running_jobs"
11
12
  require_relative "deployangel/core/protocol"
12
13
  require_relative "deployangel/core/execution_context"
13
14
  require_relative "deployangel/core/buffer"
@@ -57,6 +58,14 @@ module DeployAngel
57
58
  agent&.record_job(**attributes)
58
59
  end
59
60
 
61
+ def job_started(job_class)
62
+ agent&.job_started(job_class)
63
+ end
64
+
65
+ def job_finished(token)
66
+ agent&.job_finished(token)
67
+ end
68
+
60
69
  def record_exception(exception, **options)
61
70
  agent&.record_exception(exception, **options)
62
71
  end
@@ -128,6 +137,7 @@ require_relative "deployangel/rails/error_subscriber"
128
137
  require_relative "deployangel/tasks"
129
138
  require_relative "deployangel/metadata"
130
139
  require_relative "deployangel/rails/metadata"
140
+ require_relative "deployangel/queue_backlog"
131
141
  require_relative "deployangel/sidekiq"
132
142
  require_relative "deployangel/apartment"
133
143
  require_relative "deployangel/rails/railtie" if defined?(::Rails::Railtie)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: deployangel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.27
4
+ version: 0.1.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jordan Owens
@@ -45,6 +45,7 @@ files:
45
45
  - lib/deployangel/core/protocol.rb
46
46
  - lib/deployangel/core/redaction.rb
47
47
  - lib/deployangel/core/release.rb
48
+ - lib/deployangel/core/running_jobs.rb
48
49
  - lib/deployangel/core/transport.rb
49
50
  - lib/deployangel/credentials.rb
50
51
  - lib/deployangel/doctor.rb
@@ -52,6 +53,7 @@ files:
52
53
  - lib/deployangel/fork_hook.rb
53
54
  - lib/deployangel/mcp.rb
54
55
  - lib/deployangel/metadata.rb
56
+ - lib/deployangel/queue_backlog.rb
55
57
  - lib/deployangel/rack/http.rb
56
58
  - lib/deployangel/rails/active_job.rb
57
59
  - lib/deployangel/rails/error_subscriber.rb
@@ -86,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
86
88
  - !ruby/object:Gem::Version
87
89
  version: '0'
88
90
  requirements: []
89
- rubygems_version: 4.0.0
91
+ rubygems_version: 3.6.9
90
92
  specification_version: 4
91
93
  summary: 'DeployAngel agent for Rails: production verification for every deployment.'
92
94
  test_files: []