ruby_everywhere 0.1.4 → 0.1.5

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: 19d066bb2584933621929b270c69e6205804d07441a6e4a8bfcc052cc6483e55
4
- data.tar.gz: c337dfa569b91412f4cb03027006d3199c9f224ebe63e5c5a066396986729194
3
+ metadata.gz: e8f07eb6ae976f35ea7ccda33b87bfefd7c2d0ffd39c1b7aa1f7001bedbd482b
4
+ data.tar.gz: b0b9afc49cdc99e731c5bc7a742f8a90c4a5c94d496749131fa37b403567f91c
5
5
  SHA512:
6
- metadata.gz: b0865c4cc65cfdc86706332f2d00c704fcd361ffe72ed6bbc51100297640634d246a3537b2f4ed38318fbacacbbd490ff422e4b6449905d06918ce7663dbc2f5
7
- data.tar.gz: 119672c4cf55ea3ea05e768c3eba161ee2aa6b81b6f5f68046c6327bc99f2dcd9897658d7320e0a0d7a58cbc64d8ac457a2522d6cd71434f1df8ef974367a783
6
+ metadata.gz: f13c6059ba2dae4dbe87c21a376265ae7cef7f84a890492897694dde101e9b94bc4990df82b6efc64f278e1ed35d3d921e8bfd7d5c131763cc79589d096d4211
7
+ data.tar.gz: 741d54c87c703df48024662485d03edcc15546c20f3fbe6f6d52b2b3c6056658c8aff7f2e73eb558d7f31961aa1c65c686a656bfc5e9bf9c2d900a3c633057aa
@@ -5,6 +5,7 @@ require "fileutils"
5
5
  require "tmpdir"
6
6
  require "shellwords"
7
7
  require_relative "../shellout"
8
+ require_relative "../log_filter"
8
9
  require_relative "../png"
9
10
  require_relative "../icon"
10
11
  require_relative "../paths"
@@ -89,11 +90,11 @@ module Everywhere
89
90
  staging = File.join(Dir.mktmpdir("everywhere-press"), File.basename(output_path))
90
91
  log = "#{output_path}.build.log"
91
92
 
92
- UI.step("rbe-tebako press (Ruby #{ruby}) #{UI.dim("— grab a coffee on first run")}")
93
- UI.step(UI.dim("full build log: #{log}"))
93
+ UI.step("rbe-tebako press #{UI.dim("(Ruby #{ruby} — grab a coffee on first run)")}")
94
+ UI.detail("full build log: #{UI.short_path(log)}")
94
95
  Shellout.run_logged!(press_env, ["rbe-tebako", "press",
95
96
  "--root=#{framework.root}", "--entry-point=#{entry}",
96
- "--Ruby=#{ruby}", "--output=#{staging}"], log: log)
97
+ "--Ruby=#{ruby}", "--output=#{staging}"], log: log, filter: LogFilter.for(:tebako))
97
98
 
98
99
  # Tebako's final strip step only warns (and still exits 0) if it can't
99
100
  # write the output, so verify the artifact instead of trusting exit codes.
@@ -50,6 +50,9 @@ module Everywhere
50
50
 
51
51
  build = submit(client, targets, channel, signed_id, manifest)
52
52
  UI.success("submitted #{UI.bold(build["id"])} for #{targets.join(", ")}")
53
+ if (build_url = build["url"] || "#{client.base_url}/builds/#{build["id"]}")
54
+ UI.substep("watch it live → #{UI.cyan(build_url)}")
55
+ end
53
56
 
54
57
  return unless wait
55
58
 
@@ -31,13 +31,18 @@ module Everywhere
31
31
  option :shell_dir, default: nil, desc: "Path to the generic Tauri shell (src-tauri)"
32
32
 
33
33
  def call(artifact:, token:, url: nil, shell_dir: nil, **)
34
+ # We run headless on a build runner (a pipe, not a TTY) and stream into
35
+ # the Platform web log, which renders ANSI. Force color on so the hosted
36
+ # log reads like a local run.
37
+ ENV["CLICOLOR_FORCE"] ||= "1" unless ENV["NO_COLOR"]
38
+
34
39
  base = Everywhere::Platform::Client.base_url(url)
35
40
  @client = Everywhere::Platform::Client.new(base, token: token)
36
41
  @artifact = artifact
37
42
 
38
43
  job = get!("/runner/artifacts/#{artifact}")
39
44
  patch!("/runner/artifacts/#{artifact}", status: "running")
40
- remote_log("runner: picked up #{job.dig("target", "os")}-#{job.dig("target", "arch")} for #{job.dig("app", "name")}")
45
+ remote_log("picked up #{UI.cyan("#{job.dig("target", "os")}-#{job.dig("target", "arch")}")} for #{UI.bold(job.dig("app", "name"))}")
41
46
 
42
47
  Dir.mktmpdir("every-runner") do |work|
43
48
  src = fetch_snapshot(work, job)
@@ -63,7 +68,7 @@ module Everywhere
63
68
  # ---- pipeline steps ------------------------------------------------------
64
69
 
65
70
  def fetch_snapshot(work, job)
66
- remote_log("runner: downloading source snapshot")
71
+ remote_log("downloading source snapshot")
67
72
  tarball = File.join(work, "snapshot.tar.gz")
68
73
  @client.download(job["snapshot_url"], tarball)
69
74
  src = File.join(work, "src")
@@ -73,7 +78,7 @@ module Everywhere
73
78
  end
74
79
 
75
80
  def pull_credentials(work)
76
- remote_log("runner: pulling signing credentials")
81
+ remote_log("pulling signing credentials")
77
82
  creds = get!("/runner/artifacts/#{@artifact}/credentials")["credentials"] || []
78
83
  env = {}
79
84
  creds.each do |cred|
@@ -113,19 +118,23 @@ module Everywhere
113
118
  listing = `security find-identity -p codesigning #{@keychain.shellescape} 2>/dev/null`
114
119
  sha = listing[/\b([0-9A-F]{40})\b/, 1]
115
120
  UI.die!("no codesigning identity in the delivered .p12") unless sha
116
- remote_log("runner: signing identity #{listing[/"(.+)"/, 1]} (#{sha[0, 10]}…)")
121
+ remote_log("signing identity #{listing[/"(.+)"/, 1]} #{UI.dim("(#{sha[0, 10]}…)")}")
117
122
  sha
118
123
  end
119
124
 
120
125
  def run_release(src, job, signing_env, shell_dir)
121
- remote_log("runner: every release (press → sign → notarize → staple)")
126
+ remote_log("every release press → sign → notarize → staple")
122
127
  cmd = [RbConfig.ruby, "-I", lib_dir, every_exe, "release",
123
128
  "--root", src,
124
129
  "--target", "#{job.dig("target", "os")}-#{job.dig("target", "arch")}",
125
130
  "--channel", job.dig("target", "distribution_channel").to_s]
126
131
  cmd += ["--shell-dir", shell_dir] if shell_dir
127
132
 
128
- success = stream(signing_env, cmd)
133
+ # The web UI renders ANSI, but `every release` runs here as a pipe (not a
134
+ # TTY) so it would otherwise strip all color. Force it on — for our own
135
+ # output and for cargo — so the hosted log looks like the local one.
136
+ forced = { "CLICOLOR_FORCE" => "1", "CARGO_TERM_COLOR" => "always" }
137
+ success = stream(forced.merge(signing_env), cmd)
129
138
 
130
139
  dist = File.join(src, "dist")
131
140
  release_json = JSON.parse(File.read(File.join(dist, "release.json"))) rescue nil
@@ -134,7 +143,7 @@ module Everywhere
134
143
  end
135
144
 
136
145
  def complete_success(zip, release_json)
137
- remote_log("runner: uploading artifact (#{(File.size(zip) / 1024.0 / 1024.0).round(1)}MB)")
146
+ remote_log("uploading artifact #{UI.dim("(#{(File.size(zip) / 1024.0 / 1024.0).round(1)}MB)")}")
138
147
  signed_id = upload_artifact(zip)
139
148
  post!("/runner/artifacts/#{@artifact}/complete",
140
149
  status: "succeeded", file_signed_id: signed_id, release_json: release_json)
@@ -185,9 +194,13 @@ module Everywhere
185
194
  nil # never let a log hiccup kill the build
186
195
  end
187
196
 
197
+ # A runner-orchestration line: distinct from the `every release` steps it
198
+ # brackets (tagged "runner"), rendered once and forwarded verbatim — color
199
+ # and all — to the web log.
188
200
  def remote_log(message)
189
- UI.step(message)
190
- flush_log("#{message}\n")
201
+ line = "#{UI.gray("runner")} #{UI.cyan("→")} #{message}"
202
+ puts line
203
+ flush_log("#{line}\n")
191
204
  end
192
205
 
193
206
  # ---- helpers -------------------------------------------------------------
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "dry/cli"
4
4
  require_relative "../shellout"
5
+ require_relative "../log_filter"
5
6
  require_relative "../receipt"
6
7
  require_relative "../paths"
7
8
  require_relative "build"
@@ -48,7 +49,7 @@ module Everywhere
48
49
  # 1. Produce (or reuse) the signed-for-dev .app via the build path. The
49
50
  # notarize step below re-signs it inside-out with the Developer ID.
50
51
  unless skip_build
51
- UI.step("building #{UI.bold(app_name)} for #{UI.cyan(target)}")
52
+ UI.phase("Building #{app_name} for #{target}")
52
53
  Build.new.call(root: root, ruby: ruby, entry: entry, app_name: app_name, target: target, shell_dir: shell_dir, **)
53
54
  end
54
55
 
@@ -58,10 +59,11 @@ module Everywhere
58
59
  # 2. Sign (Developer ID + hardened runtime) → notarize → staple → verify.
59
60
  env_file ||= discover_env_file(root_path)
60
61
  env = env_file ? { "ENV_FILE" => env_file } : {}
61
- UI.step("signing + notarizing #{File.basename(app_dir)}#{env_file ? " #{UI.dim("(#{rel(env_file, root_path)})")}" : ""}")
62
+ UI.phase("Signing & notarizing #{File.basename(app_dir)}")
63
+ UI.step("preparing #{File.basename(app_dir)}#{env_file ? " #{UI.dim("(env: #{rel(env_file, root_path)})")}" : ""}")
62
64
  UI.warn("no signing env found — set APPLE_* or pass --env-file (notarize.sh will fail without it)") unless env_file || ENV["APPLE_SIGNING_IDENTITY"]
63
65
  log = "#{app_dir}.notarize.log"
64
- Shellout.run_logged!(env, ["/bin/bash", NOTARIZE, app_dir], log: log)
66
+ Shellout.run_logged!(env, ["/bin/bash", NOTARIZE, app_dir], log: log, filter: LogFilter.for(:notarize))
65
67
 
66
68
  # 3. Gather signing facts from the bundle itself, then emit the receipt.
67
69
  artifact = "#{app_dir.sub(/\.app\z/, "")}.zip" # notarize.sh re-zips the stapled app
@@ -125,18 +127,16 @@ module Everywhere
125
127
  end
126
128
 
127
129
  def summarize(out, artifact, signing, root_path)
128
- UI.success("release ready")
129
- UI.step("artifact: #{rel(artifact, root_path)} (#{(File.size(artifact) / 1024.0 / 1024.0).round}MB)")
130
- UI.step("signed by #{signing["signed_by"] || "?"} · " \
131
- "notarized #{yn(signing.dig("platform", "notarized"))} · " \
132
- "stapled #{yn(signing.dig("platform", "stapled"))} · " \
133
- "gatekeeper #{yn(signing["verified"])}")
134
- UI.step("receipt: #{rel(out, root_path)}")
130
+ UI.phase("Release ready 🎉")
131
+ UI.success("#{rel(artifact, root_path)} #{UI.dim("(#{(File.size(artifact) / 1024.0 / 1024.0).round}MB — send this to your friends)")}")
132
+ UI.substep("signed by #{UI.bold(signing["signed_by"] || "?")}")
133
+ UI.substep("notarized #{UI.yn(signing.dig("platform", "notarized"))} · " \
134
+ "stapled #{UI.yn(signing.dig("platform", "stapled"))} · " \
135
+ "gatekeeper #{UI.yn(signing["verified"])}")
136
+ UI.substep("receipt #{UI.dim(rel(out, root_path))}")
135
137
  end
136
138
 
137
- def yn(bool) = bool ? UI.green("yes") : UI.red("no")
138
-
139
- def rel(path, root_path) = path.sub("#{root_path}/", "").sub("#{Dir.pwd}/", "")
139
+ def rel(path, root_path) = UI.short_path(path.sub("#{root_path}/", "").sub("#{Dir.pwd}/", ""))
140
140
  end
141
141
  end
142
142
  end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ui"
4
+
5
+ module Everywhere
6
+ # Turns the raw, chatty output of the external build tools (tebako, the
7
+ # notarize script and the Apple tools it drives) into clean, indented, colored
8
+ # lines for the console and the Platform web log.
9
+ #
10
+ # A filter is a small state machine fed one raw line at a time. It returns:
11
+ # * a String -> print this (already colored/indented) instead of the raw line
12
+ # * :drop -> swallow the line (pure noise: repeated status, temp paths…)
13
+ # * nil -> not recognized; caller shows the raw line dimmed as a detail
14
+ #
15
+ # The FULL raw stream is always still written to the on-disk log, so nothing is
16
+ # lost — this only governs what a human sees scroll by.
17
+ class LogFilter
18
+ def self.for(profile) = new(profile)
19
+
20
+ def initialize(profile)
21
+ @profile = profile
22
+ @seen = {}
23
+ end
24
+
25
+ def call(raw)
26
+ line = raw.to_s.rstrip
27
+ return :drop if line.empty?
28
+
29
+ case @profile
30
+ when :notarize then notarize(line)
31
+ when :tebako then tebako(line)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ # --- `every release` signing pipeline (notarize.sh + Apple tools) ---------
38
+
39
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
40
+ def notarize(line)
41
+ case line
42
+ # notarize.sh's own "==> phase" markers become substeps.
43
+ when /\A==> signing with:\s*(.+)/
44
+ UI.substep_line("signing bundle #{UI.dim("(#{short_identity(Regexp.last_match(1))})")}")
45
+ when /\A==> verifying signature/
46
+ UI.substep_line("verifying signature")
47
+ when /\A==> zipping for notarization:\s*(.+)/
48
+ UI.substep_line("zipping for notarization #{UI.dim("→ #{File.basename(Regexp.last_match(1))}")}")
49
+ when /\A==> submitting to Apple notary service/
50
+ UI.substep_line("submitting to Apple notary service #{UI.dim("(this can take a few minutes)")}")
51
+ when /\A==> stapling ticket/
52
+ UI.substep_line("stapling notarization ticket")
53
+ when /\A==> Gatekeeper assessment/
54
+ UI.substep_line("Gatekeeper assessment")
55
+ when /\A==> re-zipping/
56
+ UI.substep_line("re-zipping stapled app for distribution")
57
+
58
+ # Per-binary signing chatter -> one dim line each (basename only).
59
+ when /\A\s+sign (.+)/
60
+ UI.note_line("signed #{File.basename(Regexp.last_match(1))}")
61
+ when /: replacing existing signature/
62
+ :drop
63
+
64
+ # codesign --verify / stapler / spctl outcomes.
65
+ when /: valid on disk/
66
+ UI.note_line("valid on disk", marker: "✓", color: :green)
67
+ when /: satisfies its Designated Requirement/
68
+ :drop
69
+ when /The staple and validate action worked!/
70
+ UI.note_line("stapled and validated", marker: "✓", color: :green)
71
+ when /The validate action worked!/
72
+ :drop
73
+ when /\A(.+): accepted/
74
+ UI.note_line("Gatekeeper accepted", marker: "✓", color: :green)
75
+ when /\Asource=/, /\Aorigin=/
76
+ UI.note_line(line)
77
+
78
+ # notarytool --wait progress. Collapse the repeated dotted "In Progress"
79
+ # lines into a single heartbeat, and celebrate the terminal states.
80
+ when /Current status:\s*Accepted/
81
+ UI.note_line("notarization accepted", marker: "✓", color: :green)
82
+ when /Current status:\s*(Invalid|Rejected)/
83
+ UI.note_line("notarization #{Regexp.last_match(1).downcase}", marker: "✗", color: :red)
84
+ when /Current status:\s*In Progress/
85
+ once(:progress) { UI.note_line("waiting on Apple…", marker: "⋯") }
86
+ when /\A\s*id:\s*([0-9a-fA-F-]{36})/
87
+ once(:submission) { UI.note_line("submission #{Regexp.last_match(1)[0, 8]}…") }
88
+
89
+ # Pure noise from notarytool's own prose / the final banner (release.rb
90
+ # prints its own summary, so drop notarize.sh's).
91
+ when /\AConducting pre-submission checks/,
92
+ /\ASubmission ID received/,
93
+ /\ASuccessfully uploaded file/,
94
+ /\A\s*path:/,
95
+ /\A\s*status:/,
96
+ /\AWaiting for processing/,
97
+ /\AProcessing:/,
98
+ /Processing complete/,
99
+ /\A✅ Done\./,
100
+ /Send this to your friend/,
101
+ /Stapled, so it works even offline/,
102
+ /zipping preserves the signature/
103
+ :drop
104
+
105
+ else
106
+ nil # unrecognized -> caller shows it dimmed
107
+ end
108
+ end
109
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
110
+
111
+ # --- tebako press ---------------------------------------------------------
112
+ #
113
+ # Thousands of lines of C++/Ruby build output. We can't restructure it, but
114
+ # we can keep errors/warnings legible and dim the rest into the background so
115
+ # the important lines (and our own steps around it) stand out.
116
+ def tebako(line)
117
+ short = UI.short_path(line)
118
+ case line
119
+ when /\b(error|fatal|undefined reference|cannot find)\b/i
120
+ " #{UI.red(short)}"
121
+ when /\b(warning|deprecated)\b/i
122
+ " #{UI.yellow(short)}"
123
+ when /\A-- /, /\bBuilding\b/, /\bConfiguring\b/, /\bInstalling\b/
124
+ UI.note_line(short.sub(/\A-- /, ""))
125
+ else
126
+ " #{UI.gray(short)}"
127
+ end
128
+ end
129
+
130
+ # Show a line only the first time its key is seen; :drop the repeats. Keeps a
131
+ # long notarization from scrolling a wall of identical "waiting" lines (and
132
+ # notarytool's thrice-echoed submission id from showing three times).
133
+ def once(key)
134
+ return :drop if @seen[key]
135
+
136
+ @seen[key] = true
137
+ yield
138
+ end
139
+
140
+ # "Developer ID Application: Andrea Fomera (TU4485Z3KR)" or a bare SHA-1.
141
+ def short_identity(id)
142
+ id = id.strip
143
+ return "#{id[0, 10]}…" if id.match?(/\A[0-9A-F]{40}\z/i)
144
+
145
+ id[/\(([^)]+)\)\s*\z/, 1] || id
146
+ end
147
+ end
148
+ end
@@ -31,14 +31,45 @@ module Everywhere
31
31
  ["", nil]
32
32
  end
33
33
 
34
- # Run a command streaming to the console while also capturing to a log file.
35
- def run_logged!(env, cmd, log:)
36
- require "shellwords"
37
- line = "#{cmd.shelljoin} 2>&1 | tee #{log.shellescape}"
38
- success = unbundled { system(env, "/bin/bash", "-o", "pipefail", "-c", line) }
34
+ # Run a command, capturing its full combined output to a log file while
35
+ # showing a (optionally filtered) view on the console.
36
+ #
37
+ # Pass filter: a LogFilter (or any #call(line) -> String | :drop | nil) to
38
+ # prettify what a human sees; the on-disk log always gets the raw stream, so
39
+ # nothing is lost. Unrecognized lines (filter returns nil) are shown dimmed
40
+ # and indented so they read as background detail under the current step.
41
+ def run_logged!(env, cmd, log:, filter: nil)
42
+ require "open3"
43
+ success = false
44
+ unbundled do
45
+ File.open(log, "w") do |logf|
46
+ Open3.popen2e(env, *cmd) do |stdin, out, wait|
47
+ stdin.close
48
+ out.each_line do |raw|
49
+ logf.write(raw)
50
+ emit(raw, filter)
51
+ end
52
+ success = wait.value.success?
53
+ end
54
+ end
55
+ end
39
56
  UI.die!("command failed: #{cmd.join(" ")} (full log: #{log})") unless success
40
57
  end
41
58
 
59
+ # Route one raw line to the console: verbatim when there's no filter, else
60
+ # the filter's rendition (:drop swallows it, nil falls back to dim detail).
61
+ def emit(raw, filter)
62
+ return $stdout.print(raw) unless filter
63
+
64
+ shown = filter.call(raw)
65
+ case shown
66
+ when :drop, false then nil
67
+ when nil then $stdout.puts(" #{UI.gray(UI.short_path(raw.rstrip))}")
68
+ else $stdout.puts(shown)
69
+ end
70
+ end
71
+ private_class_method :emit
72
+
42
73
  def spawn(env, cmd, chdir:)
43
74
  unbundled { Process.spawn(env, cmd, chdir: chdir) }
44
75
  end
data/lib/everywhere/ui.rb CHANGED
@@ -3,6 +3,10 @@
3
3
  module Everywhere
4
4
  # Terminal output helpers. Colors follow the informal standard:
5
5
  # on when stdout is a TTY or CLICOLOR_FORCE=1, always off when NO_COLOR is set.
6
+ #
7
+ # The hosted runner streams `every release` through a pipe (not a TTY) into the
8
+ # Platform web log, so it exports CLICOLOR_FORCE=1 to keep our color — the web
9
+ # UI renders ANSI. That's why we force rather than sniff the TTY there.
6
10
  module UI
7
11
  module_function
8
12
 
@@ -17,21 +21,76 @@ module Everywhere
17
21
  color? ? "\e[#{codes.join(";")}m#{text}\e[0m" : text.to_s
18
22
  end
19
23
 
20
- def bold(t) = paint(t, 1)
21
- def dim(t) = paint(t, 2)
22
- def red(t) = paint(t, 31)
23
- def green(t) = paint(t, 32)
24
- def yellow(t) = paint(t, 33)
25
- def cyan(t) = paint(t, 36)
24
+ def bold(t) = paint(t, 1)
25
+ def dim(t) = paint(t, 2)
26
+ def red(t) = paint(t, 31)
27
+ def green(t) = paint(t, 32)
28
+ def yellow(t) = paint(t, 33)
29
+ def blue(t) = paint(t, 34)
30
+ def magenta(t) = paint(t, 35)
31
+ def cyan(t) = paint(t, 36)
32
+ def gray(t) = paint(t, 90) # bright black — dimmer than dim() on most themes
26
33
 
27
- def step(msg) = puts("#{cyan("→")} #{msg}")
28
- def ok(msg) = puts("#{green("✓")} #{msg}")
29
- def bad(msg) = puts("#{red("✗")} #{msg}")
30
- def warn(msg) = puts("#{yellow("!")} #{msg}")
31
- def success(msg) = puts("#{green("✓")} #{bold(msg)}")
34
+ # --- output levels --------------------------------------------------------
35
+ #
36
+ # phase ● a major phase boundary (build / sign / notarize)
37
+ # step → a top-level action inside a phase
38
+ # substep ▸ a sub-action under a step (indented)
39
+ # detail · raw-ish detail under a substep (indented + dim)
40
+ #
41
+ # Every level has a *_line string builder (used by LogFilter, which returns
42
+ # lines for someone else to print) and a printer that puts it. ok / success /
43
+ # warn / bad / die! carry outcomes at any level.
44
+
45
+ def phase_line(msg) = "\n#{bold(cyan("●"))} #{bold(msg)}"
46
+ def step_line(msg) = "#{cyan("→")} #{msg}"
47
+ def substep_line(msg) = " #{cyan("▸")} #{msg}"
48
+ def detail_line(msg) = " #{gray("·")} #{gray(msg)}"
49
+ def ok_line(msg) = "#{green("✓")} #{msg}"
50
+ def bad_line(msg) = "#{red("✗")} #{msg}"
51
+ def warn_line(msg) = "#{yellow("!")} #{msg}"
52
+ def success_line(msg) = "#{green("✓")} #{bold(msg)}"
53
+
54
+ def phase(msg) = puts(phase_line(msg))
55
+ def step(msg) = puts(step_line(msg))
56
+ def substep(msg) = puts(substep_line(msg))
57
+ def detail(msg) = puts(detail_line(msg))
58
+ def ok(msg) = puts(ok_line(msg))
59
+ def bad(msg) = puts(bad_line(msg))
60
+ def warn(msg) = puts(warn_line(msg))
61
+ def success(msg) = puts(success_line(msg))
62
+
63
+ # An indented, dim outcome under a substep — e.g. "accepted", "valid on disk".
64
+ def note_line(msg, marker: "·", color: :gray)
65
+ tint = respond_to?(color) ? method(color) : method(:gray)
66
+ " #{tint.call(marker)} #{tint.call(msg)}"
67
+ end
68
+
69
+ def note(msg, marker: "·", color: :gray) = puts(note_line(msg, marker: marker, color: color))
32
70
 
33
71
  def die!(msg)
34
72
  abort "#{red("✗")} #{red(msg)}"
35
73
  end
74
+
75
+ # A yes/no fact, colored by truthiness (for signing receipts).
76
+ def yn(bool) = bool ? green("yes") : red("no")
77
+
78
+ # --- path shortening ------------------------------------------------------
79
+
80
+ # Collapse the noisy machine paths that external tools echo (codesign,
81
+ # notarytool, stapler) down to something a human can scan. In particular the
82
+ # runner works out of a deep `/var/folders/…/every-runner…/src/` sandbox — we
83
+ # strip that prefix so only the meaningful `dist/App.app` tail remains. Then
84
+ # $HOME → ~ and the cwd → a leading (dropped) prefix.
85
+ def short_path(str)
86
+ s = str.to_s.dup
87
+ # Runner/local temp sandboxes: keep only the part after the extracted src/.
88
+ s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/every-[a-z]+[0-9A-Za-z_-]+/src/}, "")
89
+ # Any other everywhere-* scratch dir: keep the basename tail.
90
+ s = s.gsub(%r{/(?:private/)?(?:var|tmp)/[^\s"']*?/everywhere-[a-z]+[0-9A-Za-z_.-]*/}, "")
91
+ home = ENV["HOME"]
92
+ s = s.gsub("#{home}/", "~/") if home && !home.empty?
93
+ s
94
+ end
36
95
  end
37
96
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Everywhere
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.5"
5
5
 
6
6
  # Version of the vendored @rubyeverywhere/bridge JS this gem ships. Tracks
7
7
  # bridge/package.json — bump it whenever lib/everywhere/javascript/bridge.js is
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_everywhere
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrea Fomera
@@ -69,6 +69,7 @@ files:
69
69
  - lib/everywhere/icon.rb
70
70
  - lib/everywhere/ignore.rb
71
71
  - lib/everywhere/javascript/bridge.js
72
+ - lib/everywhere/log_filter.rb
72
73
  - lib/everywhere/paths.rb
73
74
  - lib/everywhere/platform/client.rb
74
75
  - lib/everywhere/platform/credentials.rb