Skip to content

Commit 1840c90

Browse files
authored
Introduce Bundlebun.system(): runs Bun as a subprocess and returns. The default call() works as it was: replaces the process. exec() added for clarity. (#12)
1 parent 0959ef3 commit 1840c90

File tree

11 files changed

+393
-67
lines changed

11 files changed

+393
-67
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## [Unreleased]
22

3+
- `Bundlebun.system(args)` method: runs Bun as a subprocess and returns to Ruby. Returns `true` if Bun exited successfully. Use this when you need to continue executing Ruby code after Bun finishes.
4+
- Default behaviour remains: `Bundlebun.()` / `Bundlebun.call` / `Bundlebun.exec` all replace the current Ruby process with Bun (never return). This is what binstubs and wrappers use.
35
- Added RBS type signatures for the public API
46

57
## [0.3.0] - 2026-01-29

README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,13 +197,30 @@ bun outdated v1.1.38 (bf2f153f)
197197

198198
**Check bundlebun API: https://rubydoc.info/gems/bundlebun**.
199199

200-
The easiest way to call Bun from Ruby would be `Bundlebun.call`:
200+
The easiest way to call Bun from Ruby is via `Bundlebun.()` (shortcut for `Bundlebun.call()`).
201+
202+
**Note:** `Bundlebun.call` replaces the current Ruby process with Bun: it never returns.
203+
204+
```ruby
205+
Bundlebun.('outdated') # runs `bun outdated`
206+
Bundlebun.call(['add', 'postcss']) # runs `bun add postcss`
207+
```
208+
209+
If you need to run Bun and then continue executing your Ruby code, use `Bundlebun.system`:
201210

202211
```ruby
203-
Bundlebun.call("outdated") # => `bun outdated`
204-
Bundlebun.call(["add", "postcss"]) # => `bun add postcss`
212+
if Bundlebun.system('install')
213+
puts 'Dependencies installed!'
214+
else
215+
puts 'Installation failed'
216+
end
217+
218+
# Run tests and check result
219+
success = Bundlebun.system('test')
205220
```
206221

222+
Returns `true` if Bun exited successfully, `false` or `nil` otherwise.
223+
207224
Check out the [API documentation](https://rubydoc.info/gems/bundlebun) on `Bundlebun::Runner` for helper methods.
208225

209226
## Versioning

exe/bundlebun

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ rescue LoadError
2424
end
2525
require 'bundlebun'
2626

27-
Bundlebun::Runner.call(ARGV)
27+
Bundlebun.call(ARGV)

lib/bundlebun.rb

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,84 @@
99
# bundlebun includes binary distributions of Bun for each of the supported
1010
# platforms (macOS, Linux, Windows) and architectures.
1111
#
12+
# bundlebun provides two ways to run Bun:
13+
#
14+
# - {.call} (also available as {.exec}): Replaces the current Ruby process with Bun. The default.
15+
#
16+
# - {.system}: Runs Bun as a subprocess and returns control to Ruby.
17+
# Use this when you need to continue executing Ruby code after Bun finishes.
18+
#
1219
# @see Bundlebun::Runner
1320
# @see Bundlebun::Integrations
21+
#
22+
# @example Running Bun (replaces process, never returns)
23+
# Bundlebun.('install') # .() shorthand syntax
24+
# Bundlebun.call('outdated')
25+
# Bundlebun.call(['add', 'postcss'])
26+
#
27+
# @example Running Bun as subprocess (returns to Ruby)
28+
# if Bundlebun.system('install')
29+
# puts 'Dependencies installed!'
30+
# end
1431
module Bundlebun
1532
class << self
16-
# Runs the Bun runtime with parameters.
33+
# Replaces the current Ruby process with Bun.
1734
#
18-
# A shortcut for {Bundlebun::Runner.call}.
35+
# This is the default way to run Bun. The Ruby process is replaced by Bun
36+
# and never returns. Also available via the +.()+ shorthand syntax.
1937
#
2038
# @param arguments [String, Array<String>] Command arguments to pass to Bun
21-
# @return [Integer] Exit status code (`127` if executable not found)
39+
# @return [void] This method never returns
2240
#
23-
# @example String as an argument
24-
# Bundlebun.call('--version') # => `bun --version`
41+
# @example Basic usage
42+
# Bundlebun.call('outdated')
43+
# Bundlebun.call(['add', 'postcss'])
2544
#
26-
# @example Array of strings as an argument
27-
# Bundlebun.call(['add', 'postcss']) # => `bun add postcss`
45+
# @example Using the .() shorthand
46+
# Bundlebun.('install')
47+
# Bundlebun.(ARGV)
2848
#
49+
# @see .exec
50+
# @see .system
2951
# @see Bundlebun::Runner.call
3052
def call(...)
3153
Runner.call(...)
3254
end
3355

56+
# Replaces the current Ruby process with Bun. Same as {.call}.
57+
#
58+
# @param arguments [String, Array<String>] Command arguments to pass to Bun
59+
# @return [void] This method never returns
60+
#
61+
# @see .call
62+
# @see Bundlebun::Runner.exec
63+
def exec(...)
64+
Runner.exec(...)
65+
end
66+
67+
# Runs Bun as a subprocess and returns control to Ruby.
68+
#
69+
# Unlike {.call} and {.exec}, this method does not replace the current process.
70+
# Use this when you need to run Bun and then continue executing Ruby code.
71+
#
72+
# @param arguments [String, Array<String>] Command arguments to pass to Bun
73+
# @return [Boolean, nil] +true+ if Bun exited successfully (status 0),
74+
# +false+ if it exited with an error, +nil+ if execution failed
75+
#
76+
# @example Run install and check result
77+
# if Bundlebun.system('install')
78+
# puts 'Dependencies installed!'
79+
# else
80+
# puts 'Installation failed'
81+
# end
82+
#
83+
# @see .call
84+
# @see .exec
85+
# @see Bundlebun::Runner.system
86+
def system(...)
87+
Runner.system(...)
88+
end
89+
3490
def loader # @private
3591
@loader ||= Zeitwerk::Loader.for_gem.tap do |loader|
3692
loader.ignore("#{__dir__}/tasks")

lib/bundlebun/runner.rb

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,84 @@
33
module Bundlebun
44
# {Runner} is the class that bundlebun uses to run the bundled Bun executable.
55
#
6+
# bundlebun provides two ways to run Bun:
7+
#
8+
# - {.call} (also available as {.exec}): Replaces the current Ruby process with Bun. This is the default.
9+
#
10+
# - {.system}: Runs Bun as a subprocess and returns control to Ruby.
11+
# Use this when you need to continue executing Ruby code after Bun finishes.
12+
#
613
# @see Bundlebun
14+
#
15+
# @example Running Bun (replaces process, never returns)
16+
# Bundlebun.('install')
17+
# Bundlebun.call('outdated')
18+
# Bundlebun.call(['add', 'postcss'])
19+
#
20+
# @example Running Bun as subprocess (returns to Ruby)
21+
# if Bundlebun.system('install')
22+
# puts 'Dependencies installed!'
23+
# end
724
class Runner
825
BINSTUB_PATH = 'bin/bun'
926
RELATIVE_DIRECTORY = 'lib/bundlebun/vendor/bun'
1027

1128
class << self
12-
# Runs the Bun runtime with parameters.
29+
# Replaces the current Ruby process with Bun.
30+
#
31+
# @param arguments [String, Array<String>] Command arguments to pass to Bun
32+
# @return [void] This method never returns
1333
#
14-
# A wrapper for {Bundlebun::Runner.new}, {Bundlebun::Runner.call}.
34+
# @example In a binstub (bin/bun)
35+
# #!/usr/bin/env ruby
36+
# require 'bundlebun'
37+
# Bundlebun.exec(ARGV)
38+
#
39+
# @see .call
40+
# @see .system
41+
def exec(...)
42+
new(...).exec
43+
end
44+
45+
# Replaces the current Ruby process with Bun. Alias for {.exec}.
46+
# Also available via the +.()+ shorthand syntax.
1547
#
1648
# @param arguments [String, Array<String>] Command arguments to pass to Bun
17-
# @return [Integer] Exit status code (`127` if executable not found)
49+
# @return [void] This method never returns
1850
#
19-
# @example String as an argument
20-
# Bundlebun.call('--version') # => `bun --version`
51+
# @example Basic usage
52+
# Bundlebun.call('outdated')
53+
# Bundlebun.call(['add', 'postcss'])
2154
#
22-
# @example Array of strings as an argument
23-
# Bundlebun.call(['add', 'postcss']) # => `bun add postcss`
55+
# @example Using the .() shorthand
56+
# Bundlebun.('install')
2457
#
25-
# @see Bundlebun::Runner.new
26-
# @see Bundlebun::Runner#call
58+
# @see .exec
59+
# @see .system
2760
def call(...)
28-
new(...).call
61+
exec(...)
62+
end
63+
64+
# Runs Bun as a subprocess and returns control to Ruby.
65+
#
66+
# Unlike {.call} and {.exec}, this method does not replace the current process.
67+
# Use this when you need to run Bun and then continue executing Ruby code.
68+
#
69+
# @param arguments [String, Array<String>] Command arguments to pass to Bun
70+
# @return [Boolean, nil] +true+ if Bun exited successfully (status 0),
71+
# +false+ if it exited with an error, +nil+ if execution failed
72+
#
73+
# @example Run install and check result
74+
# if Bundlebun.system('install')
75+
# puts 'Dependencies installed!'
76+
# else
77+
# puts 'Installation failed'
78+
# end
79+
#
80+
# @see .call
81+
# @see .exec
82+
def system(...)
83+
new(...).system
2984
end
3085

3186
# A relative path to binstub that bundlebun usually generates with installation Rake tasks.
@@ -98,35 +153,64 @@ def binstub_exist?
98153
end
99154
end
100155

101-
# Intialize the {Runner} with arguments to run the Bun runtime later via #call.
156+
# Initialize the {Runner} with arguments to run the Bun runtime later.
102157
#
103158
# @param arguments [String, Array<String>] Command arguments to pass to Bun
104159
#
105160
# @example String as an argument
106-
# Bundlebun::Runner.new('--version') # => `bun --version`
161+
# Bundlebun::Runner.new('--version')
107162
#
108163
# @example Array of strings as an argument
109-
# Bundlebun::Runner.new(['add', 'postcss']) # => `bun add postcss`
164+
# Bundlebun::Runner.new(['add', 'postcss'])
110165
#
111-
# @see Bundlebun::Runner#call
166+
# @see #system
167+
# @see #exec
112168
def initialize(arguments = '')
113169
@arguments = arguments
114170
end
115171

116-
# Runs the Bun executable with previously specified arguments.
117-
#
118-
# Check other methods of {Bundlebun::Runner} to see how we determine what to run exactly.
172+
# Replaces the current Ruby process with Bun.
173+
# This is the default behavior.
119174
#
120-
# @return [Integer] Exit status code (`127` if executable not found)
175+
# @return [void] This method never returns
121176
#
122177
# @example
123-
# b = Bundlebun::Runner.new('--version')
124-
# b.call
178+
# runner = Bundlebun::Runner.new(ARGV)
179+
# runner.exec # Ruby process ends here, Bun takes over
180+
#
181+
# @see #system
182+
def exec
183+
check_executable!
184+
Kernel.exec(command)
185+
end
186+
187+
# Replaces the current Ruby process with Bun. Alias for {#exec}.
188+
#
189+
# @return [void] This method never returns
125190
#
126-
# @see Bundlebun::Runner
191+
# @see #exec
127192
def call
193+
exec
194+
end
195+
196+
# Runs Bun as a subprocess and returns control to Ruby.
197+
#
198+
# Unlike {#call} and {#exec}, this method does not replace the current process.
199+
# Use this when you need to run Bun and then continue executing Ruby code.
200+
#
201+
# @return [Boolean, nil] +true+ if Bun exited successfully (status 0),
202+
# +false+ if it exited with an error, +nil+ if execution failed
203+
#
204+
# @example
205+
# runner = Bundlebun::Runner.new('install')
206+
# if runner.system
207+
# puts 'Dependencies installed!'
208+
# end
209+
#
210+
# @see #exec
211+
def system
128212
check_executable!
129-
exec(command)
213+
Kernel.system(command)
130214
end
131215

132216
private

lib/tasks/bun.rake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,5 @@ require 'rake'
1717
desc 'Run bundled Bun with parameters. Example: rake "bun[build]"'
1818
task :bun, [:command] do |_t, args|
1919
command = args[:command] || ''
20-
Bundlebun::Runner.call(command)
20+
Bundlebun.call(command)
2121
end

sig/bundlebun.rbs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,17 @@
11
module Bundlebun
22
VERSION: String
33

4-
def self.call: (*String arguments) -> void
4+
# Replaces the current Ruby process with Bun.
5+
# This is the default way to run Bun. Never returns.
6+
def self.call: (String | Array[String] arguments) -> bot
7+
8+
# Replaces the current Ruby process with Bun. Same as .call.
9+
def self.exec: (String | Array[String] arguments) -> bot
10+
11+
# Runs Bun as a subprocess and returns control to Ruby.
12+
# Returns true if Bun exited successfully, false if it failed, nil if execution failed.
13+
def self.system: (String | Array[String] arguments) -> bool?
14+
515
def self.prepend_to_path: () -> String?
616
def self.load_tasks: () -> void
717
def self.load_integrations: () -> Array[Module]

sig/bundlebun/runner.rbs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,15 @@ module Bundlebun
33
BINSTUB_PATH: String
44
RELATIVE_DIRECTORY: String
55

6-
def self.call: (*String arguments) -> void
6+
# Replaces the current Ruby process with Bun. Never returns.
7+
def self.exec: (String | Array[String] arguments) -> bot
8+
9+
# Replaces the current Ruby process with Bun. Alias for .exec. Never returns.
10+
def self.call: (String | Array[String] arguments) -> bot
11+
12+
# Runs Bun as a subprocess and returns control to Ruby.
13+
def self.system: (String | Array[String] arguments) -> bool?
14+
715
def self.binstub_path: () -> String
816
def self.full_binstub_path: () -> String
917
def self.relative_directory: () -> String
@@ -14,6 +22,14 @@ module Bundlebun
1422
def self.binstub_exist?: () -> bool
1523

1624
def initialize: (?String | Array[String] arguments) -> void
17-
def call: () -> void
25+
26+
# Replaces the current Ruby process with Bun. Never returns.
27+
def exec: () -> bot
28+
29+
# Replaces the current Ruby process with Bun. Alias for #exec. Never returns.
30+
def call: () -> bot
31+
32+
# Runs Bun as a subprocess and returns control to Ruby.
33+
def system: () -> bool?
1834
end
1935
end

0 commit comments

Comments
 (0)