boringbuilder 0.1.0.alpha.1 → 0.1.0.alpha.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e20e908005e1a14c27c241b1540be57b73b7fa20f4a79513a22ce9535149fce4
4
- data.tar.gz: e7b7c17cf76c2fcc852dd03765c1002bb8c32a993d3aac87b0b0561ac8e7a382
3
+ metadata.gz: 23c8af7191024f581586b9b8525ab4cdede020a5fe30cfb41d700eee13240513
4
+ data.tar.gz: e08454d82aa57ee024a006fbfad9ec4eab6f58847cfed9cdea1fa9244d8b70fd
5
5
  SHA512:
6
- metadata.gz: 3950b539d458467b05b258bceb0d0e5a04f1c08fbb3dc1943db5f2750200c337694459090e5318334910be42f03495f260d134044e9a61fb4c6d4806bf4e5d9e
7
- data.tar.gz: a7c8ee8d4b21afa7362c5f15846f943eff9ef1009c40b3860eae2802cddac826a25c6bd12e8c182468237378630cfbb0dbb53c2d9d5d6d6a020976ca37e20537
6
+ metadata.gz: 9d64e03a9dec0d0df8a0e9afbb360d5dc153e6c1734f57dd32e0ddde4fbae1136f0660d5abb85ed5d31305b109cc89bcead693a943079cddbeeb43ed0916ef31
7
+ data.tar.gz: 65a7e6bf585a95ce460d6a77bf7d6de869d53673cd28dea953c03669090d12a60ee372958b62c2e3c7067501b45677cbf00842a9036059a01509553dea153cf4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.0.alpha.2 - 2026-08-27
4
+
5
+ - Stream clean, Docker-style build steps and command output through Dagger Ruby.
6
+ - Run Bundler with the available CPUs unless `BUNDLE_JOBS` is explicitly configured.
7
+ - Keep Rails artifacts identical across cold and restored dependency caches.
8
+
3
9
  ## 0.1.0.alpha.1 - 2026-08-27
4
10
 
5
11
  - Reintroduce BoringBuilder as a Ruby gem backed by Dagger Ruby.
@@ -6,7 +6,7 @@ module BoringBuilder
6
6
  class Builder
7
7
  attr_reader :configuration, :progress
8
8
 
9
- def initialize(configuration, progress: BuildProgress.silent)
9
+ def initialize(configuration, progress: DaggerRuby::Progress.silent)
10
10
  @configuration = configuration
11
11
  @progress = progress
12
12
  end
@@ -59,7 +59,7 @@ module BoringBuilder
59
59
  end
60
60
 
61
61
  def build
62
- options = {}
62
+ options = { progress: "pretty" }
63
63
  config_file = :auto
64
64
  dry_run = false
65
65
  json = false
@@ -71,6 +71,8 @@ module BoringBuilder
71
71
  return 0 if help_requested
72
72
  raise OptionParser::InvalidArgument, "expected at most one PROJECT directory" if @argv.length > 1
73
73
 
74
+ options[:progress] = nil if json
75
+
74
76
  configuration = BoringBuilder.configuration(root: @argv.first || Dir.pwd, config: config_file, **options)
75
77
  project = Project.new(configuration).validate!
76
78
  if dry_run
@@ -78,8 +80,9 @@ module BoringBuilder
78
80
  return 0
79
81
  end
80
82
 
81
- print_build_start(project, json: json)
82
- result = @builder_class.new(configuration, progress: build_progress(configuration, json: json)).build
83
+ progress = build_progress(configuration, json: json)
84
+ print_build_start(project, json: json, progress: progress)
85
+ result = @builder_class.new(configuration, progress: progress).build
83
86
  json ? @out.puts(JSON.generate(result.to_h)) : print_result(result)
84
87
  0
85
88
  end
@@ -109,7 +112,8 @@ module BoringBuilder
109
112
  parser.on("--platform PLATFORM", "Target platform, for example linux/amd64") do |value|
110
113
  options[:platform] = value
111
114
  end
112
- parser.on("--progress MODE", %w[auto plain tty], "Dagger progress: auto, plain, or tty") do |value|
115
+ parser.on("--progress MODE", %w[pretty auto plain tty dots logs],
116
+ "Build progress: pretty (default), auto, plain, tty, dots, or logs") do |value|
113
117
  options[:progress] = value
114
118
  end
115
119
  end
@@ -209,7 +213,7 @@ module BoringBuilder
209
213
  @out.puts "Artifact: #{result.artifact_id} (#{result.artifact_name})" if result.artifact_published?
210
214
  end
211
215
 
212
- def print_build_start(project, json:)
216
+ def print_build_start(project, json:, progress:)
213
217
  return if json || !dagger_session?
214
218
 
215
219
  cache = BoringCache.new(project, nil, environment: @environment).mode
@@ -217,8 +221,7 @@ module BoringBuilder
217
221
  details = [pipeline, "#{project.configuration.runtime} runtime", "#{cache} cache",
218
222
  "#{project.configuration.format.to_s.tr('_', '.')} output"]
219
223
 
220
- @out.puts "Building #{project.app_name} with Dagger"
221
- @out.puts " #{details.join(' · ')}"
224
+ progress.message("Building #{project.app_name} with Dagger\n #{details.join(' · ')}")
222
225
  end
223
226
 
224
227
  def dagger_session?
@@ -226,9 +229,8 @@ module BoringBuilder
226
229
  end
227
230
 
228
231
  def build_progress(configuration, json:)
229
- enabled = !json && dagger_session? && configuration.progress.nil?
230
- animated = enabled && @out.respond_to?(:tty?) && @out.tty?
231
- BuildProgress.new(out: @out, enabled: enabled, animated: animated)
232
+ config = DaggerRuby::Config.new(progress: configuration.progress, verify_version: false)
233
+ config.progress_reporter(out: @out, enabled: !json && dagger_session?, environment: @environment)
232
234
  end
233
235
  end
234
236
  end
@@ -10,7 +10,7 @@ module BoringBuilder
10
10
  attr_reader :project, :client, :container, :runtime, :exporters, :progress
11
11
 
12
12
  def initialize(project, client, container, runtime:, command_runner: nil, exporters: nil,
13
- progress: BuildProgress.silent)
13
+ progress: DaggerRuby::Progress.silent)
14
14
  @project = project
15
15
  @client = client
16
16
  @container = container
@@ -56,33 +56,36 @@ module BoringBuilder
56
56
  end
57
57
 
58
58
  def export_step_name(adapter)
59
- return "Publish artifact to BoringCache" if adapter.name == :boringcache
59
+ return "[export] Publish artifact to BoringCache" if adapter.name == :boringcache
60
60
 
61
- "Export #{configuration.format.to_s.tr('_', '.')} artifact"
61
+ "[export] Write #{configuration.format.to_s.tr('_', '.')} artifact"
62
62
  end
63
63
 
64
64
  def build_asset
65
65
  filename = project.output_path.basename.to_s
66
66
  case configuration.format
67
67
  when :directory
68
- Exporters::Asset.new(kind: :directory, source: artifact_directory, filename: filename)
68
+ Exporters::Asset.new(kind: :directory, source: prepared_artifact_directory, filename: filename)
69
69
  when :tar
70
70
  Exporters::Asset.new(kind: :file, source: archive_file("artifact.tar", compression: nil), filename: filename)
71
71
  when :tar_zst
72
72
  Exporters::Asset.new(kind: :file, source: archive_file("artifact.tar.zst", compression: :zstd),
73
73
  filename: filename)
74
74
  when :oci, :docker
75
- Exporters::Asset.new(kind: :file, source: container.as_tarball(media_types: media_types), filename: filename)
75
+ source = progress.step("[export] Create #{configuration.format.to_s.upcase} image archive") do
76
+ container.as_tarball(media_types: media_types).sync
77
+ end
78
+ Exporters::Asset.new(kind: :file, source: source, filename: filename)
76
79
  end
77
80
  end
78
81
 
79
82
  def export_reference
80
83
  if configuration.publish
81
- progress.step("Publish container image") do
84
+ progress.step("[export] Publish container image") do
82
85
  container.publish(configuration.publish, media_types: media_types)
83
86
  end
84
87
  elsif configuration.load
85
- progress.step("Load container image") { load_image }
88
+ progress.step("[export] Load container image") { load_image }
86
89
  end
87
90
  end
88
91
 
@@ -123,20 +126,48 @@ module BoringBuilder
123
126
  @artifact_directory ||= project.artifact.export_directory(client, container)
124
127
  end
125
128
 
129
+ def prepared_artifact_directory
130
+ @prepared_artifact_directory ||= progress.step("[export] Assemble artifact filesystem") do
131
+ artifact_directory.sync
132
+ end
133
+ end
134
+
126
135
  def archive_file(filename, compression:)
127
- packer = client.container.from(PACKER_IMAGE).with_exec(%w[apk add --no-cache tar zstd])
128
- packer = packer.with_mounted_directory("/artifact", artifact_directory)
136
+ packer = progress.step("[export] FROM #{PACKER_IMAGE}") { client.container.from(PACKER_IMAGE).sync }
137
+ packer = run_archive_step(packer, %w[apk add --no-cache tar zstd], "[export] RUN apk add tar zstd")
138
+ packer = packer.with_mounted_directory("/artifact", prepared_artifact_directory)
129
139
  tar_path = compression == :zstd ? "/artifact.tar" : "/#{filename}"
130
- packer = packer.with_exec(
140
+ packer = run_archive_step(
141
+ packer,
131
142
  [
132
143
  "tar", "--sort=name", "--mtime=@0", "--owner=0", "--group=0", "--numeric-owner",
133
144
  "--pax-option=delete=atime,delete=ctime", "-C", "/artifact", "-cf", tar_path, "."
134
- ]
145
+ ],
146
+ "[export] RUN tar -cf #{tar_path} ."
135
147
  )
136
- packer = packer.with_exec(["zstd", "-q", "-T1", tar_path, "-o", "/#{filename}"]) if compression == :zstd
148
+ if compression == :zstd
149
+ packer = run_archive_step(
150
+ packer,
151
+ ["zstd", "-q", "-T0", tar_path, "-o", "/#{filename}"],
152
+ "[export] RUN zstd -T0 #{tar_path}"
153
+ )
154
+ end
137
155
  packer.file("/#{filename}")
138
156
  end
139
157
 
158
+ def run_archive_step(container, command, name)
159
+ executed = container.with_exec(command)
160
+ progress.step(name) do
161
+ output = command_output(executed)
162
+ progress.write(output) unless progress.streaming?
163
+ executed.sync
164
+ end
165
+ end
166
+
167
+ def command_output(container)
168
+ [container.stdout, container.stderr].map(&:strip).reject(&:empty?).join("\n")
169
+ end
170
+
140
171
  def media_types
141
172
  configuration.format == :docker ? :DockerMediaTypes : :OCIMediaTypes
142
173
  end
@@ -40,7 +40,7 @@ module BoringBuilder
40
40
  at: CACHE_PATH,
41
41
  entry: "mise",
42
42
  workdir: workdir,
43
- name: "Prepare Mise toolchain"
43
+ name: "[build] RUN #{command.join(' ')}"
44
44
  )
45
45
  end
46
46
 
@@ -4,7 +4,7 @@ module BoringBuilder
4
4
  class Pipeline
5
5
  attr_reader :project, :client, :progress
6
6
 
7
- def initialize(project, client, environment: ENV, progress: BuildProgress.silent)
7
+ def initialize(project, client, environment: ENV, progress: DaggerRuby::Progress.silent)
8
8
  @project = project
9
9
  @client = client
10
10
  @environment = environment
@@ -124,7 +124,10 @@ module BoringBuilder
124
124
  return container if container.equal?(@last_synced_container)
125
125
 
126
126
  @last_synced_container = progress.step(name) do
127
- progress.write { output.call } if output
127
+ if output
128
+ result = output.call
129
+ progress.write(result) unless progress.streaming?
130
+ end
128
131
  container.sync
129
132
  end
130
133
  end
@@ -5,6 +5,7 @@ module BoringBuilder
5
5
  DEFAULT_EXCLUDES = %w[
6
6
  .env
7
7
  .env.*
8
+ .bundle/cache
8
9
  .git
9
10
  coverage
10
11
  dist
@@ -29,7 +30,7 @@ module BoringBuilder
29
30
  self
30
31
  end
31
32
 
32
- def container(client, progress: BuildProgress.silent)
33
+ def container(client, progress: DaggerRuby::Progress.silent)
33
34
  return Pipeline.new(self, client, progress: progress).build(&configuration.pipeline) if custom_pipeline?
34
35
 
35
36
  builder = rails? ? RailsBuild : RubyBuild
@@ -20,23 +20,29 @@ module BoringBuilder
20
20
  "BUNDLE_CLEAN" => "true",
21
21
  "BUNDLE_DEPLOYMENT" => "1",
22
22
  "BUNDLE_IGNORE_CONFIG" => "true",
23
- "BUNDLE_JOBS" => "1",
24
23
  "BUNDLE_PATH" => "/usr/local/bundle",
25
24
  "BUNDLE_WITHOUT" => "development:test"
26
25
  }.freeze
27
- BUNDLE_INSTALL_COMMAND = ["sh", "-c", "bundle install && bundle clean --force"].freeze
26
+ BUNDLE_INSTALL_COMMAND = [
27
+ "sh", "-c",
28
+ 'export BUNDLE_JOBS="${BUNDLE_JOBS:-$(nproc)}"; bundle install && bundle clean --force'
29
+ ].freeze
30
+ APT_INSTALL_COMMAND = [
31
+ "sh", "-c",
32
+ "apt-get update && apt-get install -y --no-install-recommends \"$@\" && rm -rf /var/lib/apt/lists/*",
33
+ "apt-get"
34
+ ].freeze
28
35
  BUNDLE_FILES = %w[Gemfile Gemfile.lock .ruby-version].freeze
29
36
  BUNDLE_CONFIG = <<~YAML
30
37
  ---
31
38
  BUNDLE_PATH: "vendor/bundle"
32
39
  BUNDLE_WITHOUT: "development:test"
33
- BUNDLE_JOBS: "1"
34
40
  BUNDLE_CLEAN: "true"
35
41
  YAML
36
42
 
37
43
  attr_reader :project, :client, :progress
38
44
 
39
- def initialize(project, client, progress: BuildProgress.silent)
45
+ def initialize(project, client, progress: DaggerRuby::Progress.silent)
40
46
  @project = project
41
47
  @client = client
42
48
  @progress = progress
@@ -44,15 +50,21 @@ module BoringBuilder
44
50
 
45
51
  def container
46
52
  builder = with_environment(build_image_container, build_environment)
47
- builder = install_packages(builder, (BUILD_PACKAGES + configuration.build_packages).uniq)
48
- builder = create_application_user(builder)
53
+ builder = install_packages(builder, (BUILD_PACKAGES + configuration.build_packages).uniq, stage: :build)
54
+ builder = create_application_user(builder, stage: :build)
49
55
  builder = install_toolchain(builder)
50
56
  builder = install_gems(builder)
51
- builder = builder.with_directory(application_path, project.source(client), owner: user_owner)
52
- builder = builder.with_workdir(application_path).with_exec(%w[bundle check])
57
+ builder = pipeline.step("[build] COPY application source", builder) do |container|
58
+ container.with_directory(application_path, project.source(client), owner: user_owner)
59
+ end
60
+ builder = pipeline.exec(
61
+ builder,
62
+ %w[bundle check],
63
+ name: "[build] RUN bundle check",
64
+ workdir: application_path
65
+ )
53
66
  builder = precompile(builder)
54
- builder = write_bundle_config(builder)
55
- builder = progress.step("Build #{project.framework.to_s.capitalize} application") { builder.sync }
67
+ builder = pipeline.step("[build] Write Bundler configuration", builder) { write_bundle_config(_1) }
56
68
 
57
69
  runtime_container(builder)
58
70
  end
@@ -77,12 +89,12 @@ module BoringBuilder
77
89
 
78
90
  def build_image_container
79
91
  address = configuration.base_image || DEFAULT_BUILD_IMAGE
80
- client.container(image_options).from(address)
92
+ progress.step("[build] FROM #{address}") { client.container(image_options).from(address).sync }
81
93
  end
82
94
 
83
95
  def runtime_image_container
84
96
  address = configuration.base_image || DEFAULT_RUNTIME_IMAGE
85
- client.container(image_options).from(address)
97
+ progress.step("[runtime] FROM #{address}") { client.container(image_options).from(address).sync }
86
98
  end
87
99
 
88
100
  def build_environment
@@ -119,7 +131,7 @@ module BoringBuilder
119
131
  cache: "bundle",
120
132
  at: "/usr/local/bundle/cache",
121
133
  entry: "bundler",
122
- name: "Install gems"
134
+ name: "[build] RUN bundle install"
123
135
  )
124
136
  container.with_exec(["find", "/usr/local/bundle", "-path", "*/cache/*.gem", "-type", "f", "-delete"])
125
137
  .with_exec([
@@ -130,6 +142,7 @@ module BoringBuilder
130
142
 
131
143
  def write_bundle_config(container)
132
144
  container.with_workdir(application_path)
145
+ .with_exec(%w[rm -rf .bundle/cache])
133
146
  .with_exec(%w[mkdir -p .bundle])
134
147
  .with_new_file("#{application_path}/.bundle/config", BUNDLE_CONFIG)
135
148
  end
@@ -140,41 +153,59 @@ module BoringBuilder
140
153
 
141
154
  def runtime_container(builder)
142
155
  container = with_environment(runtime_image_container, Mise::ENVIRONMENT.merge(runtime_environment))
143
- container = install_packages(container, (RUNTIME_PACKAGES + configuration.runtime_packages).uniq)
144
- container = create_application_user(container)
145
- container = container.with_directory("/mise/installs", builder.directory("/mise/installs"))
146
- .with_directory("/usr/local/bundle", builder.directory("/usr/local/bundle"),
147
- owner: user_owner)
148
- .with_directory(application_path, builder.directory(application_path), owner: user_owner)
149
- .with_workdir(application_path)
150
- .with_exec(%w[mise reshim])
156
+ container = install_packages(container, (RUNTIME_PACKAGES + configuration.runtime_packages).uniq,
157
+ stage: :runtime)
158
+ container = create_application_user(container, stage: :runtime)
159
+ container = pipeline.step("[runtime] COPY toolchain, gems, and application", container) do |runtime|
160
+ runtime.with_directory("/mise/installs", builder.directory("/mise/installs"))
161
+ .with_directory("/usr/local/bundle", builder.directory("/usr/local/bundle"), owner: user_owner)
162
+ .with_directory(application_path, builder.directory(application_path), owner: user_owner)
163
+ end
164
+ container = pipeline.exec(
165
+ container,
166
+ %w[mise reshim],
167
+ name: "[runtime] RUN mise reshim",
168
+ workdir: application_path
169
+ )
151
170
  container = runtime_metadata(container).with_user(project.application_user)
152
- progress.step("Assemble runtime image") { container.sync }
171
+ progress.step("[runtime] Configure image") { container.sync }
153
172
  end
154
173
 
155
- def install_packages(container, packages)
156
- container
157
- .with_exec(%w[apt-get update])
158
- .with_exec(["apt-get", "install", "-y", "--no-install-recommends", *packages])
159
- .with_exec(%w[rm -rf /var/lib/apt/lists])
174
+ def install_packages(container, packages, stage:)
175
+ pipeline.exec(
176
+ container,
177
+ [*APT_INSTALL_COMMAND, *packages],
178
+ name: "[#{stage}] RUN apt-get update && apt-get install packages"
179
+ )
160
180
  end
161
181
 
162
- def create_application_user(container)
182
+ def create_application_user(container, stage:)
163
183
  name = project.application_user
164
- container
165
- .with_exec(["groupadd", "--system", "--gid", "1000", name])
166
- .with_exec(["useradd", name, "--uid", "1000", "--gid", "1000", "--create-home", "--shell", "/bin/bash"])
184
+ pipeline.step("[#{stage}] Create application user", container) do |step|
185
+ step.with_exec(["groupadd", "--system", "--gid", "1000", name])
186
+ .with_exec(["useradd", name, "--uid", "1000", "--gid", "1000", "--create-home", "--shell", "/bin/bash"])
187
+ end
167
188
  end
168
189
 
169
190
  def precompile(container)
170
- container = container.with_exec(%w[bin/rails assets:precompile]) if project.rails? && project.assets?
191
+ if project.rails? && project.assets?
192
+ container = pipeline.exec(
193
+ container,
194
+ %w[bin/rails assets:precompile],
195
+ name: "[build] RUN bin/rails assets:precompile"
196
+ )
197
+ end
171
198
  if project.bootsnap?
172
199
  paths = project.rails? ? %w[app/ lib/] : %w[app/ lib/ config/]
173
- container = container.with_exec(["bundle", "exec", "bootsnap", "precompile", "--gemfile", *paths])
200
+ container = pipeline.exec(
201
+ container,
202
+ ["bundle", "exec", "bootsnap", "precompile", "--gemfile", *paths],
203
+ name: "[build] RUN bundle exec bootsnap precompile"
204
+ )
174
205
  end
175
206
  return container unless project.rails?
176
207
 
177
- normalize_rails_output(container)
208
+ pipeline.step("[build] Normalize Rails output", container) { normalize_rails_output(_1) }
178
209
  end
179
210
 
180
211
  def normalize_rails_output(container)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module BoringBuilder
4
- VERSION = "0.1.0.alpha.1"
4
+ VERSION = "0.1.0.alpha.2"
5
5
  end
data/lib/boringbuilder.rb CHANGED
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dagger_ruby"
4
+
3
5
  require_relative "boringbuilder/version"
4
6
  require_relative "boringbuilder/errors"
5
7
  require_relative "boringbuilder/artifact"
6
8
  require_relative "boringbuilder/configuration"
7
9
  require_relative "boringbuilder/config_file"
8
10
  require_relative "boringbuilder/initializer"
9
- require_relative "boringbuilder/build_progress"
10
11
  require_relative "boringbuilder/boring_cache"
11
12
  require_relative "boringbuilder/mise"
12
13
  require_relative "boringbuilder/pipeline"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: boringbuilder
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.alpha.1
4
+ version: 0.1.0.alpha.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-08-27 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: dagger_ruby
@@ -16,14 +15,14 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: '0.10'
18
+ version: '0.11'
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: '0.10'
25
+ version: '0.11'
27
26
  description: BoringBuilder turns application source into filesystem artifacts and
28
27
  container images. Rails is first class, while Mise-powered Dagger Ruby recipes support
29
28
  any workload.
@@ -48,7 +47,6 @@ files:
48
47
  - lib/boringbuilder.rb
49
48
  - lib/boringbuilder/artifact.rb
50
49
  - lib/boringbuilder/boring_cache.rb
51
- - lib/boringbuilder/build_progress.rb
52
50
  - lib/boringbuilder/builder.rb
53
51
  - lib/boringbuilder/cli.rb
54
52
  - lib/boringbuilder/config_file.rb
@@ -80,13 +78,12 @@ licenses:
80
78
  metadata:
81
79
  allowed_push_host: https://rubygems.org
82
80
  homepage_uri: https://github.com/boringcache/builder
83
- source_code_uri: https://github.com/boringcache/builder/tree/v0.1.0.alpha.1
81
+ source_code_uri: https://github.com/boringcache/builder/tree/v0.1.0.alpha.2
84
82
  documentation_uri: https://github.com/boringcache/builder#readme
85
83
  changelog_uri: https://github.com/boringcache/builder/blob/main/CHANGELOG.md
86
84
  bug_tracker_uri: https://github.com/boringcache/builder/issues
87
85
  security_policy_uri: https://github.com/boringcache/builder/security/policy
88
86
  rubygems_mfa_required: 'true'
89
- post_install_message:
90
87
  rdoc_options: []
91
88
  require_paths:
92
89
  - lib
@@ -97,12 +94,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
94
  version: 3.2.0
98
95
  required_rubygems_version: !ruby/object:Gem::Requirement
99
96
  requirements:
100
- - - ">"
97
+ - - ">="
101
98
  - !ruby/object:Gem::Version
102
- version: 1.3.1
99
+ version: '0'
103
100
  requirements: []
104
- rubygems_version: 3.4.19
105
- signing_key:
101
+ rubygems_version: 4.0.16
106
102
  specification_version: 4
107
103
  summary: Build deployment artifacts and images with Dagger
108
104
  test_files: []
@@ -1,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BoringBuilder
4
- class BuildProgress
5
- SPINNER_FRAMES = %w[⠋ ⠙ ⠹ ⠸ ⠼ ⠴ ⠦ ⠧ ⠇ ⠏].freeze
6
-
7
- def self.silent
8
- @silent ||= new(enabled: false)
9
- end
10
-
11
- def initialize(out: $stdout, enabled: true, animated: false)
12
- @out = out
13
- @enabled = enabled
14
- @animated = animated
15
- @step = 0
16
- end
17
-
18
- def step(name)
19
- return yield unless @enabled
20
-
21
- number = next_step
22
- started_at = monotonic_time
23
- @out.puts "##{number} #{name}"
24
- @active_spinner = start_spinner(number, started_at)
25
- result = yield
26
- stop_active_spinner
27
- @out.puts "##{number} DONE #{elapsed_since(started_at)}"
28
- result
29
- rescue StandardError
30
- stop_active_spinner
31
- @out.puts "##{number} ERROR #{elapsed_since(started_at)}" if number
32
- raise
33
- ensure
34
- stop_active_spinner
35
- end
36
-
37
- def write(output = nil)
38
- return unless @enabled
39
-
40
- output = yield if block_given?
41
- stop_active_spinner
42
- text = output.to_s
43
- text.each_line { |line| @out.print " #{line}" }
44
- @out.puts unless text.empty? || text.end_with?("\n")
45
- end
46
-
47
- private
48
-
49
- def next_step
50
- @step += 1
51
- end
52
-
53
- def start_spinner(number, started_at)
54
- return unless @animated
55
-
56
- Thread.new do
57
- frame = 0
58
- loop do
59
- @out.print "\r##{number} #{SPINNER_FRAMES.fetch(frame % SPINNER_FRAMES.length)} #{elapsed_since(started_at)}"
60
- @out.flush
61
- frame += 1
62
- sleep 0.1
63
- end
64
- rescue IOError
65
- nil
66
- end
67
- end
68
-
69
- def stop_spinner(thread)
70
- return unless thread&.alive?
71
-
72
- thread.kill
73
- thread.join
74
- @out.print "\r\e[2K"
75
- end
76
-
77
- def stop_active_spinner
78
- stop_spinner(@active_spinner)
79
- @active_spinner = nil
80
- end
81
-
82
- def monotonic_time
83
- Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
- end
85
-
86
- def elapsed_since(started_at)
87
- format("%.1fs", monotonic_time - started_at)
88
- end
89
- end
90
- end