fastlane-plugin-bugsee 1.0.2 → 1.1.0

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.
@@ -1,11 +1,46 @@
1
+ require 'fastlane/plugin/bugsee/helper/bugsee_handshake'
2
+
1
3
  module Fastlane
2
4
  module Actions
3
5
 
6
+ # Upload iOS dSYM(s) to Bugsee, optionally collecting the
7
+ # project's CocoaPods/SPM/Carthage dependency graph and the
8
+ # latest Xcode xcactivitylog-derived build timings in the
9
+ # same lane action.
10
+ #
11
+ # ## Cross-producer handshake
12
+ #
13
+ # When the Bugsee iOS SDK's `tools.bundle/BugseeAgent` build
14
+ # phase is wired into the Xcode project, it runs alongside
15
+ # fastlane and may already have handled some of these
16
+ # actions for this build. Before invoking BugseeAgent, this
17
+ # action reads the handshake manifest (see
18
+ # `Fastlane::Bugsee::Handshake`) and skips per-action work
19
+ # that's already been done.
20
+ #
21
+ # The handshake is checked for THREE independent actions:
22
+ #
23
+ # - `dsym_upload` — the primary purpose of this action.
24
+ # - `deps_collection` — Podfile/Package.resolved/Cartfile
25
+ # parsing + upload.
26
+ # - `timings` — xcactivitylog parsing + upload.
27
+ #
28
+ # When ALL three are handled by another producer for this
29
+ # build, the action skips invoking BugseeAgent entirely. When
30
+ # SOME are handled, the relevant `--no-*` flags are passed to
31
+ # BugseeAgent so only the missing work runs.
32
+ #
33
+ # The `:force` ConfigItem skips the handshake check entirely.
34
+ # Useful for re-uploads / debugging.
4
35
  class UploadSymbolsToBugseeAction < Action
36
+
37
+ BUGSEE_AGENT_PATH = File.expand_path(
38
+ File.join(File.dirname(__FILE__), '..', '..', '..', '..', '..', 'BugseeAgent'))
39
+
5
40
  def self.run(params)
6
41
  app_token = params[:app_token]
7
42
  host = params[:host] || "https://api.bugsee.com"
8
- agent_path = params[:agent_path] || Dir["./**/BugseeAgent"].first
43
+ agent_path = params[:agent_path] || BUGSEE_AGENT_PATH
9
44
  build_dir = params[:build_dir] || "./"
10
45
 
11
46
  UI.user_error!("Please provide an app token using app_token:") unless app_token
@@ -26,26 +61,70 @@ module Fastlane
26
61
 
27
62
  dsym_paths.each do |path|
28
63
  print(path)
29
- UI.user_error!("dSYM does not exist at path: #{path}") unless File.exists?(path)
64
+ UI.user_error!("dSYM does not exist at path: #{path}") unless File.exist?(path)
30
65
  end
31
66
 
32
- if dsym_paths.length > 0
33
- # Got here from download dsyms
34
- command = []
35
- command << agent_path.shellescape
36
- command << "-e #{host}"
37
- command << "-v #{version}" if version
38
- command << "-b #{build}" if build
39
- command << "-d #{build_dir}"
40
- command << "-m #{maps}" if symbol_maps
41
- command << "-x -l #{app_token}"
42
- command += dsym_paths
43
- begin
44
- Actions.sh(command.join(" "), log: false)
45
- rescue => ex
46
- UI.error ex.to_s # it fails, however we don't want to fail everything just for this
67
+ # ──────────────────────────────────────────────────────
68
+ # Cross-producer handshake
69
+ # ──────────────────────────────────────────────────────
70
+ skip_dsym = false
71
+ skip_deps = false
72
+ skip_timings = false
73
+ unless params[:force]
74
+ manifest = Fastlane::Bugsee::Handshake.find_manifest(
75
+ search_root: Dir.pwd,
76
+ version_name: version,
77
+ version_code: build,
78
+ )
79
+ skip_dsym = Fastlane::Bugsee::Handshake.handled_by_other?(manifest, 'dsym_upload')
80
+ skip_deps = Fastlane::Bugsee::Handshake.handled_by_other?(manifest, 'deps_collection')
81
+ skip_timings = Fastlane::Bugsee::Handshake.handled_by_other?(manifest, 'timings')
82
+
83
+ [['dsym_upload', skip_dsym],
84
+ ['deps_collection', skip_deps],
85
+ ['timings', skip_timings]].each do |action_name, handled|
86
+ next unless handled
87
+ UI.important(Fastlane::Bugsee::Handshake.skip_message(manifest, action_name))
47
88
  end
48
89
  end
90
+
91
+ # Strip dSYM paths when the handshake says they're already
92
+ # handled — sending them along would make BugseeAgent
93
+ # re-upload them since it interprets positional args as
94
+ # dSYMs to push.
95
+ effective_dsyms = skip_dsym ? [] : dsym_paths
96
+
97
+ # When there's nothing for BugseeAgent to do at all,
98
+ # short-circuit before constructing the command.
99
+ if effective_dsyms.empty? && skip_deps && skip_timings
100
+ UI.message("Bugsee: nothing to do — every action this lane would have run is already handled by another Bugsee producer for this build")
101
+ return
102
+ end
103
+
104
+ # If we have NO dSYMs (either none provided OR all
105
+ # handled) AND deps/timings have work to do, the BugseeAgent
106
+ # invocation has to be non-dSYM. The agent's main() runs
107
+ # the deps + timings pipeline unconditionally when
108
+ # collect_deps is enabled and is told it's not dealing
109
+ # with dSYMs — passing zero positional args + the
110
+ # "external" -x flag is the existing shape for that.
111
+ command = []
112
+ command << agent_path.shellescape
113
+ command << "-e #{host}"
114
+ command << "-v #{version}" if version
115
+ command << "-b #{build}" if build
116
+ command << "-d #{build_dir}"
117
+ command << "-m #{symbol_maps}" if symbol_maps
118
+ command << "--no-deps" if skip_deps
119
+ command << "--no-timings" if skip_timings
120
+ command << "-x -l #{app_token}"
121
+ command += effective_dsyms
122
+
123
+ begin
124
+ Actions.sh(command.join(" "), log: false)
125
+ rescue => ex
126
+ UI.error ex.to_s # it fails, however we don't want to fail everything just for this
127
+ end
49
128
  end
50
129
 
51
130
 
@@ -55,7 +134,7 @@ module Fastlane
55
134
  #####################################################
56
135
 
57
136
  def self.description
58
-
137
+ "Upload iOS dSYM(s) to Bugsee for crash symbolication. Also collects dependencies + build timings when not already handled by the Bugsee iOS SDK's tools.bundle/BugseeAgent build phase."
59
138
  end
60
139
 
61
140
  def self.details
@@ -107,7 +186,13 @@ module Fastlane
107
186
  FastlaneCore::ConfigItem.new(key: :build_dir,
108
187
  env_name: "TARGET_BUILD_DIR",
109
188
  description: "Target build directory",
110
- optional: true)
189
+ optional: true),
190
+ FastlaneCore::ConfigItem.new(key: :force,
191
+ env_name: "BUGSEE_FORCE",
192
+ description: "Skip the cross-producer handshake check and run every action this lane was configured for, even if the Bugsee iOS SDK BugseeAgent build phase already handled some of them",
193
+ is_string: false,
194
+ default_value: false,
195
+ optional: true)
111
196
  ]
112
197
  end
113
198
 
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Fastlane
6
+ module Bugsee
7
+ # Cross-producer build-action handshake.
8
+ #
9
+ # When more than one Bugsee producer can run on a build —
10
+ # typically the Bugsee Android Gradle plugin OR the Bugsee iOS
11
+ # SDK's `tools.bundle/BugseeAgent` build phase running ALONGSIDE
12
+ # this fastlane plugin — we want only ONE producer to handle
13
+ # each action (mapping upload, dSYM upload, deps collection,
14
+ # build timings, etc.). Otherwise the same payload reaches the
15
+ # server twice; the server dedupes by hash so it's not
16
+ # *broken*, but the lane log gets noisy and the customer pays
17
+ # for redundant bandwidth.
18
+ #
19
+ # Each producer that runs writes a small JSON manifest
20
+ # declaring which actions it handled for THIS build. The
21
+ # fastlane plugin reads the most recent matching manifest
22
+ # before each action and skips work the other producer already
23
+ # did.
24
+ #
25
+ # ## Manifest locations
26
+ #
27
+ # - **Bugsee Android Gradle plugin** writes
28
+ # `<project>/**/build/intermediates/bugsee/<variant>/build-actions.json`
29
+ # from its existing variant tasks (same parent dir as
30
+ # `build-id.txt`).
31
+ # - **Bugsee iOS SDK `tools.bundle/BugseeAgent`** writes
32
+ # `<srcroot>/build/bugsee/build-actions.json` from its
33
+ # post-build Xcode build phase.
34
+ #
35
+ # Both formats share the JSON shape below.
36
+ #
37
+ # ## Manifest schema (version 1)
38
+ #
39
+ # {
40
+ # "schema_version": 1,
41
+ # "producer": "bugsee-android-gradle-plugin"
42
+ # | "bugsee-ios-sdk-tools-bundle",
43
+ # "producer_version": "1.2.3",
44
+ # "build_id": "<BUILD_UUID>",
45
+ # "produced_at_ms": 1700000000000,
46
+ # "version_name": "1.2.3",
47
+ # "version_code": "42",
48
+ # "actions": {
49
+ # "mapping_upload": true | false,
50
+ # "dsym_upload": true | false,
51
+ # "deps_collection": true | false,
52
+ # "timings": true | false,
53
+ # "size_analysis": true | false,
54
+ # "artifact_upload": true | false
55
+ # }
56
+ # }
57
+ #
58
+ # A `true` value means the named producer ran that action for
59
+ # this build (whether it succeeded or failed — retrying isn't
60
+ # fastlane's concern). A `false` or absent value means the
61
+ # producer was either not configured to handle it or skipped
62
+ # for this variant. Unknown keys are reserved for forward
63
+ # compatibility; readers must ignore them.
64
+ #
65
+ # ## Staleness window
66
+ #
67
+ # A manifest is considered valid only when its `produced_at_ms`
68
+ # is within {STALENESS_WINDOW_S} seconds of `Time.now`. A stale
69
+ # manifest from last week's build is treated as absent; fastlane
70
+ # does the work itself.
71
+ #
72
+ # ## Build-identity match
73
+ #
74
+ # If the action passes explicit `:version` / `:build`
75
+ # ConfigItems, those are checked against the manifest's
76
+ # `version_name` / `version_code`. A mismatch indicates the
77
+ # manifest is from a different build (developer's previous
78
+ # archive, CI's previous run, etc.) and is treated as absent.
79
+ # When the action doesn't pass them, build-identity matching
80
+ # is skipped and the manifest is honored as long as it's not
81
+ # stale.
82
+ #
83
+ # ## Override
84
+ #
85
+ # Every action that uses this helper takes a `:force` ConfigItem
86
+ # that short-circuits the handshake check. Useful for one-off
87
+ # re-uploads, debugging, or cases where the user wants fastlane
88
+ # to do the work regardless of what the other producer claims.
89
+ module Handshake
90
+ # Seconds — manifests older than this are treated as absent.
91
+ # A normal CI run is rare to last more than an hour; a local
92
+ # dev rerun the next morning genuinely is a different build.
93
+ STALENESS_WINDOW_S = 60 * 60
94
+
95
+ # The canonical action keys the manifest's `actions` block
96
+ # uses. Producers SHOULD emit only these — unknown keys are
97
+ # ignored by readers but may be reserved for future features.
98
+ ACTION_KEYS = %w[
99
+ mapping_upload
100
+ dsym_upload
101
+ deps_collection
102
+ timings
103
+ size_analysis
104
+ artifact_upload
105
+ ].freeze
106
+
107
+ # Filesystem glob patterns for locating manifests. The list
108
+ # is ordered so that a project laid out for both platforms
109
+ # at once (e.g. a cross-platform fastlane lane) picks the
110
+ # most-likely-relevant manifest first.
111
+ MANIFEST_GLOBS = [
112
+ # Android Gradle plugin output. The `**/` covers multi-
113
+ # module projects where the `app/` module nests below a
114
+ # top-level checkout. The intermediates dir is per-variant
115
+ # so a project shipping multiple variants will produce
116
+ # multiple manifests; the staleness + identity check
117
+ # narrows to the right one.
118
+ "**/build/intermediates/bugsee/*/build-actions.json",
119
+ # iOS SDK BugseeAgent output. Single file per project — no
120
+ # variant slot since iOS targets multiplex through one
121
+ # build phase.
122
+ "**/build/bugsee/build-actions.json",
123
+ ].freeze
124
+
125
+ # Locate the manifest most likely to describe THIS build.
126
+ #
127
+ # @param search_root [String, nil] directory to search under.
128
+ # Defaults to current working directory (most fastlane
129
+ # lanes cd to the project root before invoking actions).
130
+ # @param version_name [String, nil] expected android:versionName
131
+ # / iOS CFBundleShortVersionString. When given, the manifest
132
+ # must match — used to reject stale manifests from a
133
+ # previous build at a different version.
134
+ # @param version_code [String, Integer, nil] expected
135
+ # android:versionCode / iOS CFBundleVersion. Same role.
136
+ # @param now [Time] for tests — production callers omit it.
137
+ # @return [Hash, nil] the parsed manifest, or `nil` when no
138
+ # valid manifest was found.
139
+ def self.find_manifest(search_root: Dir.pwd,
140
+ version_name: nil,
141
+ version_code: nil,
142
+ now: Time.now)
143
+ candidates = MANIFEST_GLOBS.flat_map do |glob|
144
+ Dir.glob(File.join(search_root, glob))
145
+ end
146
+ # Sort newest-first by file mtime; then validity-check.
147
+ # First valid wins.
148
+ candidates
149
+ .sort_by { |p| -File.mtime(p).to_f }
150
+ .each do |path|
151
+ parsed = safe_parse(path)
152
+ next unless parsed
153
+ next unless valid_for_build?(
154
+ parsed,
155
+ version_name: version_name,
156
+ version_code: version_code,
157
+ now: now,
158
+ )
159
+ return parsed
160
+ end
161
+ nil
162
+ end
163
+
164
+ # Per-action skip check.
165
+ #
166
+ # @param manifest [Hash, nil] the manifest returned by
167
+ # {find_manifest}, or `nil` when none was found.
168
+ # @param action [String] one of {ACTION_KEYS}.
169
+ # @return [Boolean] true when ANOTHER producer ran this
170
+ # action and fastlane should skip; false otherwise
171
+ # (manifest absent, action not listed, action listed false).
172
+ def self.handled_by_other?(manifest, action)
173
+ return false if manifest.nil?
174
+ actions = manifest['actions']
175
+ return false unless actions.is_a?(Hash)
176
+ actions[action] == true
177
+ end
178
+
179
+ # Convenience: when {handled_by_other?} is true, build a
180
+ # log line the action can surface so the user understands
181
+ # why fastlane is skipping. Returns nil otherwise.
182
+ def self.skip_message(manifest, action)
183
+ return nil unless handled_by_other?(manifest, action)
184
+ producer = manifest['producer'] || 'another Bugsee producer'
185
+ version = manifest['producer_version']
186
+ version_str = version ? " (#{version})" : ''
187
+ "Bugsee: skipping #{action} — already handled by #{producer}#{version_str} for this build"
188
+ end
189
+
190
+ # @api private
191
+ # Parse + structurally validate a manifest file. Returns the
192
+ # parsed Hash on success, nil on any error. Errors are
193
+ # SILENT — a malformed manifest must not break the fastlane
194
+ # lane.
195
+ def self.safe_parse(path)
196
+ return nil unless File.file?(path)
197
+ raw = File.read(path)
198
+ return nil if raw.nil? || raw.empty?
199
+ parsed = JSON.parse(raw)
200
+ return nil unless parsed.is_a?(Hash)
201
+ return nil unless parsed['schema_version'] == 1
202
+ parsed
203
+ rescue JSON::ParserError, Errno::ENOENT, Errno::EACCES
204
+ nil
205
+ end
206
+
207
+ # @api private
208
+ # Apply the staleness window + build-identity match.
209
+ def self.valid_for_build?(manifest, version_name:, version_code:, now:)
210
+ produced_at_ms = manifest['produced_at_ms']
211
+ return false unless produced_at_ms.is_a?(Numeric)
212
+ age_s = now.to_f - (produced_at_ms.to_f / 1000.0)
213
+ # Negative age (manifest from the future — clock skew or
214
+ # tampered file) is rejected. Excess positive age too.
215
+ return false if age_s < 0
216
+ return false if age_s > STALENESS_WINDOW_S
217
+
218
+ if version_name && !version_name.to_s.empty?
219
+ return false unless manifest['version_name'].to_s == version_name.to_s
220
+ end
221
+ if version_code && !version_code.to_s.empty?
222
+ return false unless manifest['version_code'].to_s == version_code.to_s
223
+ end
224
+ true
225
+ end
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module Fastlane
6
+ module Bugsee
7
+ # Cross-language `UUID.nameUUIDFromBytes` equivalent.
8
+ #
9
+ # The Bugsee Android SDK (Java) computes synthesized BUILD_UUIDs
10
+ # via {@code java.util.UUID.nameUUIDFromBytes(bytes)}. Per RFC
11
+ # 4122 / the Java spec, that's MD5 over the input bytes with two
12
+ # bit twiddles:
13
+ #
14
+ # - byte 6, upper nibble: clear, then set to 0011 (UUID
15
+ # version 3 — name-based, MD5)
16
+ # - byte 8, upper two bits: clear, then set to 10 (variant
17
+ # RFC 4122)
18
+ #
19
+ # The Bugsee Rust CLI's `bugsee-cli` replicates the same
20
+ # primitive for ProGuard-mapping-content-derived UUIDs (the
21
+ # `bugsee_cli_proguard_uuid_compat` parity is already proven and
22
+ # locked in). This Ruby implementation closes the loop for the
23
+ # fastlane plugin's mapping-upload action: when no explicit
24
+ # `:uuid` is passed and no Gradle-plugin-emitted `build-uuid.txt`
25
+ # is found, the action synthesizes a UUID Ruby-side from
26
+ # `(app_token, version_name, version_code)` and uploads the
27
+ # mapping with that UUID; the SDK (7.0.0-beta13+) computes the
28
+ # same bytes at runtime via the same primitive and the server
29
+ # matches.
30
+ #
31
+ # This module has ZERO fastlane dependencies — the unit tests can
32
+ # require it directly without loading the whole fastlane runtime.
33
+ module Uuid
34
+ # ASCII Unit Separator (0x1F). Same byte the SDK uses to join
35
+ # the synthesis inputs (see
36
+ # `BugseeEnvironment.BUILD_ID_SYNTHESIS_SEPARATOR`). Guaranteed
37
+ # never to appear in any of the three inputs (app token is
38
+ # ASCII hex, versionName is dotted, versionCode is digits).
39
+ SEPARATOR = ""
40
+
41
+ # Ruby equivalent of Java's `UUID.nameUUIDFromBytes(bytes)`.
42
+ # Takes a String (treated as UTF-8 bytes) or a Binary String,
43
+ # returns the canonical 8-4-4-4-12 lowercase UUID string.
44
+ #
45
+ # NOT a cryptographic primitive. MD5 is deprecated for security
46
+ # uses but the Java spec mandates it for `nameUUIDFromBytes`,
47
+ # and we MUST stay byte-for-byte compatible with the SDK side.
48
+ def self.name_uuid_from_bytes(input)
49
+ # Force UTF-8 encoding before digest so that a future caller
50
+ # passing an ASCII-8BIT (binary) string doesn't accidentally
51
+ # round-trip through a different code path. Digest::MD5 sees
52
+ # bytes either way, but normalizing upstream removes one
53
+ # category of "why don't these match" puzzles.
54
+ bytes = input.dup.force_encoding(Encoding::UTF_8).bytes
55
+ md5 = Digest::MD5.digest(bytes.pack('C*'))
56
+ raw = md5.bytes
57
+
58
+ # UUID version (4 high bits of byte 6) = 3 (name-based, MD5).
59
+ raw[6] = (raw[6] & 0x0f) | 0x30
60
+ # UUID variant (2 high bits of byte 8) = RFC 4122 (binary 10).
61
+ raw[8] = (raw[8] & 0x3f) | 0x80
62
+
63
+ hex = raw.map { |b| b.to_s(16).rjust(2, '0') }.join
64
+ "#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}"
65
+ end
66
+
67
+ # Synthesize a BUILD_UUID matching what the Bugsee Android SDK
68
+ # 7.0.0-beta13+ computes at runtime when neither the asset
69
+ # channel nor the manifest meta-data channel populated.
70
+ #
71
+ # Formula (must stay byte-for-byte aligned with the SDK side —
72
+ # see {@code BugseeEnvironment.synthesizeBuildIdFromVersion}):
73
+ #
74
+ # name_uuid_from_bytes(
75
+ # app_token + 0x1F + version_name + 0x1F + version_code
76
+ # )
77
+ #
78
+ # @param app_token [String] the Bugsee application token
79
+ # (mandatory — disambiguates apps within
80
+ # the same Bugsee organisation).
81
+ # @param version_name [String, nil] android:versionName from
82
+ # the merged manifest. {@code nil}
83
+ # coerces to empty string (matches the
84
+ # SDK side's null-versionName coercion).
85
+ # @param version_code [Integer, String] android:versionCode.
86
+ # Coerced to base-10 string. The SDK uses
87
+ # {@code getLongVersionCode().toString()};
88
+ # we accept either an Integer or the
89
+ # pre-stringified form so the fastlane
90
+ # ConfigItem can take either.
91
+ def self.synthesize_build_uuid(app_token, version_name, version_code)
92
+ raise ArgumentError, "app_token is required" if app_token.nil? || app_token.to_s.empty?
93
+
94
+ version_name_str = version_name.nil? ? "" : version_name.to_s
95
+ version_code_str = version_code.nil? ? "0" : version_code.to_s
96
+ input = "#{app_token}#{SEPARATOR}#{version_name_str}#{SEPARATOR}#{version_code_str}"
97
+ name_uuid_from_bytes(input)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -1,5 +1,5 @@
1
1
  module Fastlane
2
2
  module Bugsee
3
- VERSION = "1.0.2"
3
+ VERSION = "1.1.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fastlane-plugin-bugsee
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dmitry Fink
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2019-01-31 00:00:00.000000000 Z
10
+ date: 2026-10-08 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: pry
@@ -94,23 +93,26 @@ dependencies:
94
93
  - - ">="
95
94
  - !ruby/object:Gem::Version
96
95
  version: 2.2.0
97
- description:
98
96
  email: finik@bugsee.net
99
97
  executables: []
100
98
  extensions: []
101
99
  extra_rdoc_files: []
102
100
  files:
101
+ - BugseeAgent
103
102
  - LICENSE
104
103
  - README.md
105
104
  - lib/fastlane/plugin/bugsee.rb
105
+ - lib/fastlane/plugin/bugsee/actions/upload_artifact_to_bugsee.rb
106
+ - lib/fastlane/plugin/bugsee/actions/upload_mapping_to_bugsee.rb
106
107
  - lib/fastlane/plugin/bugsee/actions/upload_symbols_to_bugsee.rb
108
+ - lib/fastlane/plugin/bugsee/helper/bugsee_handshake.rb
107
109
  - lib/fastlane/plugin/bugsee/helper/bugsee_helper.rb
110
+ - lib/fastlane/plugin/bugsee/helper/bugsee_uuid.rb
108
111
  - lib/fastlane/plugin/bugsee/version.rb
109
112
  homepage: https://github.com/bugsee/fastlane-plugin-bugsee
110
113
  licenses:
111
114
  - MIT
112
115
  metadata: {}
113
- post_install_message:
114
116
  rdoc_options: []
115
117
  require_paths:
116
118
  - lib
@@ -125,9 +127,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
127
  - !ruby/object:Gem::Version
126
128
  version: '0'
127
129
  requirements: []
128
- rubyforge_project:
129
- rubygems_version: 2.5.2.3
130
- signing_key:
130
+ rubygems_version: 3.6.2
131
131
  specification_version: 4
132
132
  summary: Bugsee symbols uploader
133
133
  test_files: []