process_bot 0.1.24 → 0.1.27

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: '00099fd449ad79c57c052e10342f74b462ac806decd93e5989e0ad12620f42c3'
4
- data.tar.gz: 40f29096a018160dca9e7ed7e5e0f5513876271259fe5dc44a4cdee0b8f38013
3
+ metadata.gz: 016c72db98675857edb0e34b274af5cc835d0cd5496d4bb6fccb7ebdaf05c555
4
+ data.tar.gz: 51a18efde6ff957df0dfe6f8e07e9df2f341a1519b3f17356de1ef7422f21e85
5
5
  SHA512:
6
- metadata.gz: ec891a1ce47000bd291c9980baccfc42f8e35d0a39c53292759d2c4f810c223668a06c4d525690bdb230aef881cd36d34695c9ae73e33ed7c504dc16289715e4
7
- data.tar.gz: b51ee6883d7f5bcb338512153f0d40073b003011de87dd91c40a5e2201b4ee58999679379f696517804112791e54450ad80a73df8341c52a46f23737ed9f0438
6
+ metadata.gz: f2387a26b91b32675f6679af84f4e4096bde512f62f344a962a0312abea804395e6013b88f268c20094a04724f27c71680c421f63893c785f47586b472f68ff8
7
+ data.tar.gz: '0491239c4e11851cfef5704cbbc9e79611c356595192f5187199fd3a7c898f7e61e5885a302ed84cdedb53692a4bf7e89369791fe69a169663273d95b5f4ebd8'
data/CHANGELOG.md CHANGED
@@ -9,6 +9,8 @@
9
9
  - Guard stop-related process scanning when subprocess PID/PGID is unavailable and fail stop loudly.
10
10
  - Wait briefly for subprocess PID assignment during stop; raise if PID is still missing so stop cannot silently succeed.
11
11
 
12
+ - Require an active runner for custom stop commands to avoid constructing a fresh runner with no PID.
13
+
12
14
  ## [0.1.0] - 2022-04-03
13
15
 
14
16
  - Initial release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- process_bot (0.1.24)
4
+ process_bot (0.1.27)
5
5
  knjrbfw (>= 0.0.116)
6
6
  pry
7
7
  rake
data/README.md CHANGED
@@ -113,7 +113,9 @@ bundle exec process_bot --command start --log-file-path /var/log/process_bot.log
113
113
 
114
114
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
115
115
 
116
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
116
+ To install this gem onto your local machine, run `bundle exec rake install`.
117
+
118
+ To bump the patch version, run bundle, commit the version bump, build, and push the gem, run `bundle exec rake release:patch`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
117
119
 
118
120
  ## Contributing
119
121
 
data/Rakefile CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
-
6
- RSpec::Core::RakeTask.new(:spec)
7
-
8
5
  require "rubocop/rake_task"
9
6
 
7
+ RSpec::Core::RakeTask.new(:spec)
10
8
  RuboCop::RakeTask.new
11
9
 
10
+ Dir[File.expand_path("../lib/tasks/**/*.rake", __FILE__)].each { |f| load f }
11
+
12
12
  task default: %i[spec rubocop]
@@ -48,7 +48,14 @@ class ProcessBot::Process::Handlers::Custom
48
48
  end
49
49
 
50
50
  def stop(**_args)
51
+ runner = process.active_runner
52
+
53
+ unless runner
54
+ logger.logs "No active runner to stop"
55
+ return
56
+ end
57
+
51
58
  logger.logs "Stop related processes"
52
- process.runner.stop_related_processes
59
+ runner.stop_related_processes
53
60
  end
54
61
  end
@@ -3,7 +3,7 @@ require "json"
3
3
  require "monitor"
4
4
  require "string-cases"
5
5
 
6
- class ProcessBot::Process
6
+ class ProcessBot::Process # rubocop:disable Metrics/ClassLength
7
7
  extend Forwardable
8
8
 
9
9
  def_delegator :handler_instance, :graceful
@@ -131,8 +131,15 @@ class ProcessBot::Process
131
131
  def send_control_command(command, **command_options)
132
132
  logger.logs "Sending #{command} command"
133
133
  response = client.send_command(command: command, options: options.options.merge(command_options))
134
- raise "No response from ProcessBot while sending #{command}" if response == :nil
134
+
135
+ if response == :nil
136
+ handle_missing_control_command_response(command)
137
+ return if options[:ignore_no_process_bot]
138
+
139
+ raise "No response from ProcessBot while sending #{command}"
140
+ end
135
141
  rescue Errno::ECONNREFUSED => e
142
+ handle_missing_control_command_response(command)
136
143
  raise e unless options[:ignore_no_process_bot]
137
144
  end
138
145
 
@@ -140,6 +147,17 @@ class ProcessBot::Process
140
147
  current_runner_instance&.runner || @runner ||= build_runner
141
148
  end
142
149
 
150
+ def active_runner
151
+ current_runner_instance&.runner
152
+ end
153
+
154
+ def active_runner!
155
+ runner_instance = active_runner
156
+ return runner_instance if runner_instance
157
+
158
+ raise "Unable to stop custom process because no active runner is available. Ensure the custom command runs in foreground."
159
+ end
160
+
143
161
  def update_process_title
144
162
  process_args = {application: options[:application], handler: handler_name, id: options[:id], pid: current_pid, port: port}
145
163
  @current_process_title = "ProcessBot #{JSON.generate(process_args)}"
@@ -263,6 +281,65 @@ class ProcessBot::Process
263
281
  start_runner_instance
264
282
  end
265
283
 
284
+ def handle_missing_control_command_response(command)
285
+ return unless command == "stop"
286
+
287
+ matching_processes = matching_process_bot_processes
288
+ log_missing_control_response_diagnostics(matching_processes)
289
+ force_stop_process_bot_if_configured(matching_processes)
290
+ end
291
+
292
+ def log_missing_control_response_diagnostics(matching_processes)
293
+ logger.logs "Control command response missing; attempting diagnostics for application=#{options[:application].inspect} id=#{options[:id].inspect}"
294
+ logger.logs "Matching process_bot lines:\n#{matching_process_bot_processes_text(matching_processes)}"
295
+ end
296
+
297
+ def matching_process_bot_processes_text(lines)
298
+ return "(none)" if lines.empty?
299
+
300
+ lines.join("\n")
301
+ end
302
+
303
+ def matching_process_bot_processes
304
+ ps_output = Knj::Os.shellcmd("ps -eo pid,args")
305
+
306
+ ps_output
307
+ .to_s
308
+ .split("\n")
309
+ .select { |line| process_bot_process_line_matches?(line) }
310
+ end
311
+
312
+ def process_bot_process_line_matches?(line)
313
+ line.include?("ProcessBot {") &&
314
+ line.include?("\"application\":\"#{options[:application]}\"") &&
315
+ line.include?("\"id\":\"#{options[:id]}\"")
316
+ end
317
+
318
+ def force_stop_process_bot_if_configured(matching_processes)
319
+ return unless truthy_option?(:force_stop_on_no_response)
320
+
321
+ matching_processes.each do |line|
322
+ pid = line.strip.split(/\s+/, 2).first
323
+ next unless pid&.match?(/\A\d+\z/)
324
+
325
+ logger.logs "Force-stopping unresponsive process_bot PID #{pid}"
326
+ Process.kill("TERM", Integer(pid, 10))
327
+ rescue Errno::ESRCH
328
+ logger.logs "Process bot PID #{pid} already gone during force stop"
329
+ end
330
+ end
331
+
332
+ def truthy_option?(key)
333
+ value = options[key]
334
+ return false if value.nil?
335
+ return value if value == true || value == false
336
+
337
+ normalized = value.to_s.strip.downcase
338
+ return false if normalized == "false" || normalized == "0" || normalized == ""
339
+
340
+ true
341
+ end
342
+
266
343
  private
267
344
 
268
345
  attr_reader :current_runner_instance, :runner_events, :runner_monitor
@@ -1,3 +1,3 @@
1
1
  module ProcessBot
2
- VERSION = "0.1.24".freeze
2
+ VERSION = "0.1.27".freeze
3
3
  end
@@ -0,0 +1,171 @@
1
+ require "English"
2
+ require "fileutils"
3
+ require "pathname"
4
+ require "rubygems/version"
5
+ require "shellwords"
6
+
7
+ class ProcessBotRubygemsRelease # rubocop:disable Lint/ConstantDefinitionInBlock
8
+ VERSION_FILE = Pathname.new(File.expand_path("../process_bot/version.rb", __dir__)) unless const_defined?(:VERSION_FILE)
9
+
10
+ def call
11
+ ensure_clean_worktree!
12
+ checkout_master!
13
+ fetch!
14
+ merge!
15
+
16
+ next_version = determine_next_version
17
+
18
+ bump_version!(next_version)
19
+ commit!(next_version)
20
+ push!
21
+ gem_file = build_gem!(next_version)
22
+ push_gem!(gem_file)
23
+ delete_gem_file!(gem_file)
24
+ rescue StandardError
25
+ warn "Release failed."
26
+ raise
27
+ end
28
+
29
+ private
30
+
31
+ def ensure_clean_worktree!
32
+ dirty_entries = git_status_lines.grep_v(%r{\A\?\? process_bot-[^/]+\.gem\z})
33
+ return if dirty_entries.empty?
34
+
35
+ raise "Working tree must be clean before releasing:\n#{dirty_entries.join("\n")}"
36
+ end
37
+
38
+ def checkout_master!
39
+ run!("git", "checkout", "master")
40
+ end
41
+
42
+ def fetch!
43
+ run!("git", "fetch", remote_name)
44
+ end
45
+
46
+ def merge!
47
+ run!("git", "merge", "--ff-only", "#{remote_name}/master")
48
+ end
49
+
50
+ def determine_next_version
51
+ requested_version || bumped_version
52
+ end
53
+
54
+ def requested_version
55
+ version = ENV["VERSION"]&.strip
56
+ return if version.to_s.empty?
57
+
58
+ Gem::Version.new(version)
59
+ version
60
+ end
61
+
62
+ def bumped_version
63
+ case bump_type
64
+ when "major"
65
+ [version_segments[0] + 1, 0, 0].join(".")
66
+ when "minor"
67
+ [version_segments[0], version_segments[1] + 1, 0].join(".")
68
+ when "patch"
69
+ [version_segments[0], version_segments[1], version_segments[2] + 1].join(".")
70
+ else
71
+ raise "Unsupported BUMP=#{bump_type.inspect}. Use patch, minor, major, or VERSION=x.y.z."
72
+ end
73
+ end
74
+
75
+ def version_segments
76
+ @version_segments ||= begin
77
+ segments = Gem::Version.new(current_version).segments
78
+ segments << 0 while segments.length < 3
79
+ segments
80
+ end
81
+ end
82
+
83
+ def current_version
84
+ @current_version ||= VERSION_FILE.read[/VERSION = "([^"]+)"\.freeze/, 1] || raise("Could not find current version")
85
+ end
86
+
87
+ def bump_version!(next_version)
88
+ raise "Next version must differ from current version" if next_version == current_version
89
+
90
+ VERSION_FILE.write(
91
+ VERSION_FILE.read.sub(
92
+ /VERSION = "[^"]+"\.freeze/,
93
+ %(VERSION = "#{next_version}".freeze)
94
+ )
95
+ )
96
+
97
+ run!("git", "add", VERSION_FILE.to_s)
98
+ end
99
+
100
+ def commit!(next_version)
101
+ run!("git", "commit", "-m", "Release #{next_version}")
102
+ end
103
+
104
+ def push!
105
+ run!("git", "push", remote_name, "master")
106
+ end
107
+
108
+ def build_gem!(next_version)
109
+ gem_file = "process_bot-#{next_version}.gem"
110
+ run!("gem", "build", "process_bot.gemspec")
111
+ gem_file
112
+ end
113
+
114
+ def push_gem!(gem_file)
115
+ run!("gem", "push", gem_file)
116
+ end
117
+
118
+ def delete_gem_file!(gem_file)
119
+ FileUtils.rm_f(gem_file)
120
+ end
121
+
122
+ def git_status_lines
123
+ capture!("git", "status", "--porcelain").split("\n").reject(&:empty?)
124
+ end
125
+
126
+ def bump_type
127
+ ENV.fetch("BUMP", "patch")
128
+ end
129
+
130
+ def remote_name
131
+ ENV.fetch("REMOTE", "origin")
132
+ end
133
+
134
+ def capture!(*command)
135
+ output = `#{command.map { |part| Shellwords.escape(part) }.join(" ")}`
136
+ raise "Command failed: #{command.join(' ')}" unless $CHILD_STATUS&.success?
137
+
138
+ output
139
+ end
140
+
141
+ def run!(*command)
142
+ return if system(*command)
143
+
144
+ raise "Command failed: #{command.join(' ')}"
145
+ end
146
+ end
147
+
148
+ namespace :release do
149
+ desc "Release a patch version from master by fetching, fast-forward merging, bumping version, pushing, and publishing"
150
+ task :patch do
151
+ ENV["BUMP"] = "patch"
152
+ ProcessBotRubygemsRelease.new.call
153
+ end
154
+
155
+ desc "Release a minor version from master by fetching, fast-forward merging, bumping version, pushing, and publishing"
156
+ task :minor do
157
+ ENV["BUMP"] = "minor"
158
+ ProcessBotRubygemsRelease.new.call
159
+ end
160
+
161
+ desc "Release a major version from master by fetching, fast-forward merging, bumping version, pushing, and publishing"
162
+ task :major do
163
+ ENV["BUMP"] = "major"
164
+ ProcessBotRubygemsRelease.new.call
165
+ end
166
+
167
+ desc "Release the gem from master by fetching, fast-forward merging, bumping version, pushing, and publishing"
168
+ task :rubygems do
169
+ ProcessBotRubygemsRelease.new.call
170
+ end
171
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: process_bot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.24
4
+ version: 0.1.27
5
5
  platform: ruby
6
6
  authors:
7
7
  - kaspernj
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-02-12 00:00:00.000000000 Z
11
+ date: 2026-04-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: knjrbfw
@@ -175,6 +175,7 @@ files:
175
175
  - lib/process_bot/process/runner.rb
176
176
  - lib/process_bot/process/runner_instance.rb
177
177
  - lib/process_bot/version.rb
178
+ - lib/tasks/release.rake
178
179
  - peak_flow.yml
179
180
  - process_bot.gemspec
180
181
  homepage: https://github.com/kaspernj/process_bot