bundleup-sdk 0.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 95aacf3ac3efd394ecf74d205e774de2b0d3aeb68b547ca2ff1fce1d17372ad5
4
+ data.tar.gz: 11e31c4b1f8f281fbfcac14c68301a3bac673ab90465cf967f9bdc3250fc3557
5
+ SHA512:
6
+ metadata.gz: 17a5a80b9b93634f6c8467fb95b6ef63ab447fef316d3d971aeb3f562feb3f9f6d7d2fc661b15a1661c4a85456a9db18018e4eeabf117ede2c226940561fb8fc
7
+ data.tar.gz: f13f062aaf534759784470397a260bfa4159ff70488df01784959c1e08c446a7693c24057369539bf28844ed623dce59a5ea67be108f5c0d5a2747947af2ac81
data/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-01-21
9
+
10
+ ### Added
11
+ - Initial release of BundleUp Ruby SDK
12
+ - Support for Connections, Integrations, and Webhooks resources
13
+ - Proxy API for direct integration calls
14
+ - Unify API with Chat, Git, and PM modules
15
+ - Comprehensive error handling with custom exception classes
16
+ - Full HTTP method support (GET, POST, PUT, PATCH, DELETE)
17
+ - Automatic retry logic with exponential backoff
18
+ - JSON request/response handling
19
+ - Complete YARD documentation
20
+
21
+ [0.1.0]: https://github.com/bundleup/bundleup-sdk-ruby/releases/tag/v0.1.0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BundleUp
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,312 @@
1
+ # BundleUp Ruby SDK
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/bundleup.svg)](https://badge.fury.io/rb/bundleup)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+
6
+ Official Ruby client library for the [BundleUp](https://bundleup.io) API. Connect to 100+ integrations with a single, unified API.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'bundleup'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ ```bash
19
+ bundle install
20
+ ```
21
+
22
+ Or install it yourself as:
23
+
24
+ ```bash
25
+ gem install bundleup
26
+ ```
27
+
28
+ ## Requirements
29
+
30
+ - Ruby 2.7 or higher
31
+ - Bundler
32
+
33
+ ## Quick Start
34
+
35
+ ```ruby
36
+ require 'bundleup'
37
+
38
+ # Initialize the client with your API key
39
+ client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
40
+
41
+ # List all connections
42
+ connections = client.connections.list
43
+ puts connections
44
+
45
+ # Create a new connection
46
+ new_connection = client.connections.create({
47
+ name: 'My Connection',
48
+ integration_id: 'integration_123'
49
+ })
50
+ ```
51
+
52
+ ## Authentication
53
+
54
+ The BundleUp SDK uses API keys for authentication. You can obtain your API key from the [BundleUp Dashboard](https://app.bundleup.io).
55
+
56
+ ```ruby
57
+ # Initialize with API key
58
+ client = Bundleup::Client.new('your_api_key_here')
59
+
60
+ # Or use environment variable (recommended)
61
+ client = Bundleup::Client.new(ENV['BUNDLEUP_API_KEY'])
62
+ ```
63
+
64
+ **Security Best Practice:** Never commit your API keys to version control. Use environment variables or a secure credential management system.
65
+
66
+ ## Usage
67
+
68
+ ### Connections
69
+
70
+ Manage your integration connections:
71
+
72
+ ```ruby
73
+ # List all connections
74
+ connections = client.connections.list
75
+
76
+ # List with pagination
77
+ connections = client.connections.list(limit: 10, page: 1)
78
+
79
+ # Retrieve a specific connection
80
+ connection = client.connections.retrieve('conn_123')
81
+
82
+ # Create a new connection
83
+ connection = client.connections.create({
84
+ name: 'GitHub Connection',
85
+ integration_id: 'int_github'
86
+ })
87
+
88
+ # Update a connection
89
+ updated = client.connections.update('conn_123', {
90
+ name: 'Updated GitHub Connection'
91
+ })
92
+
93
+ # Delete a connection
94
+ client.connections.delete('conn_123')
95
+ ```
96
+
97
+ ### Integrations
98
+
99
+ Work with available integrations:
100
+
101
+ ```ruby
102
+ # List all integrations
103
+ integrations = client.integrations.list
104
+
105
+ # Retrieve a specific integration
106
+ integration = client.integrations.retrieve('int_123')
107
+ ```
108
+
109
+ ### Webhooks
110
+
111
+ Manage webhook subscriptions:
112
+
113
+ ```ruby
114
+ # List all webhooks
115
+ webhooks = client.webhooks.list
116
+
117
+ # Create a webhook
118
+ webhook = client.webhooks.create({
119
+ url: 'https://example.com/webhook',
120
+ events: ['connection.created', 'connection.deleted']
121
+ })
122
+
123
+ # Retrieve a webhook
124
+ webhook = client.webhooks.retrieve('webhook_123')
125
+
126
+ # Update a webhook
127
+ updated = client.webhooks.update('webhook_123', {
128
+ url: 'https://example.com/new-webhook'
129
+ })
130
+
131
+ # Delete a webhook
132
+ client.webhooks.delete('webhook_123')
133
+ ```
134
+
135
+ ### Proxy API
136
+
137
+ Make direct calls to the underlying integration APIs:
138
+
139
+ ```ruby
140
+ # Initialize proxy for a connection
141
+ proxy = client.proxy('conn_123')
142
+
143
+ # Make GET request
144
+ users = proxy.get('/api/users')
145
+
146
+ # Make POST request
147
+ new_user = proxy.post('/api/users', {
148
+ name: 'John Doe',
149
+ email: 'john@example.com'
150
+ })
151
+
152
+ # Make PUT request
153
+ updated_user = proxy.put('/api/users/123', {
154
+ name: 'Jane Doe'
155
+ })
156
+
157
+ # Make PATCH request
158
+ patched_user = proxy.patch('/api/users/123', {
159
+ email: 'jane@example.com'
160
+ })
161
+
162
+ # Make DELETE request
163
+ proxy.delete('/api/users/123')
164
+ ```
165
+
166
+ ### Unify API
167
+
168
+ Access unified, normalized data across different integrations:
169
+
170
+ #### Chat (Slack, Discord, Microsoft Teams, etc.)
171
+
172
+ ```ruby
173
+ # Get unified API instances for a connection
174
+ unify = client.unify('conn_123')
175
+
176
+ # List channels
177
+ channels = unify[:chat].channels(limit: 100)
178
+
179
+ # List channels with pagination
180
+ channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
181
+
182
+ # Include raw response from the integration
183
+ channels = unify[:chat].channels(limit: 100, include_raw: true)
184
+ ```
185
+
186
+ #### Git (GitHub, GitLab, Bitbucket, etc.)
187
+
188
+ ```ruby
189
+ unify = client.unify('conn_123')
190
+
191
+ # List repositories
192
+ repos = unify[:git].repos(limit: 50)
193
+
194
+ # List pull requests for a repository
195
+ pulls = unify[:git].pulls('owner/repo', limit: 20)
196
+
197
+ # List tags for a repository
198
+ tags = unify[:git].tags('owner/repo')
199
+
200
+ # List releases for a repository
201
+ releases = unify[:git].releases('owner/repo', limit: 10)
202
+
203
+ # Include raw response
204
+ repos = unify[:git].repos(include_raw: true)
205
+ ```
206
+
207
+ #### Project Management (Jira, Linear, Asana, etc.)
208
+
209
+ ```ruby
210
+ unify = client.unify('conn_123')
211
+
212
+ # List issues
213
+ issues = unify[:pm].issues(limit: 100)
214
+
215
+ # List with pagination
216
+ issues = unify[:pm].issues(limit: 50, cursor: 'next_page_token')
217
+
218
+ # Include raw response
219
+ issues = unify[:pm].issues(include_raw: true)
220
+ ```
221
+
222
+ ## Error Handling
223
+
224
+ The SDK provides custom exception classes for different error scenarios:
225
+
226
+ ```ruby
227
+ begin
228
+ client = Bundleup::Client.new('invalid_key')
229
+ connections = client.connections.list
230
+ rescue Bundleup::AuthenticationError => e
231
+ puts "Authentication failed: #{e.message}"
232
+ rescue Bundleup::InvalidRequestError => e
233
+ puts "Invalid request: #{e.message}"
234
+ rescue Bundleup::APIError => e
235
+ puts "API error: #{e.message}"
236
+ rescue Bundleup::Error => e
237
+ puts "General error: #{e.message}"
238
+ end
239
+ ```
240
+
241
+ ### Exception Classes
242
+
243
+ - `Bundleup::Error` - Base class for all BundleUp errors
244
+ - `Bundleup::APIError` - Raised when an API error occurs
245
+ - `Bundleup::AuthenticationError` - Raised when authentication fails
246
+ - `Bundleup::InvalidRequestError` - Raised when a request is invalid
247
+
248
+ ## Advanced Usage
249
+
250
+ ### Custom Connection Configuration
251
+
252
+ ```ruby
253
+ # The SDK uses Faraday under the hood with automatic retries
254
+ # Retry logic is configured with:
255
+ # - Max retries: 3
256
+ # - Initial interval: 0.5 seconds
257
+ # - Backoff factor: 2
258
+ ```
259
+
260
+ ### Pagination
261
+
262
+ Most list endpoints support pagination:
263
+
264
+ ```ruby
265
+ # Using limit and page
266
+ connections = client.connections.list(limit: 10, page: 1)
267
+
268
+ # Using cursor-based pagination (for Unify API)
269
+ channels = unify[:chat].channels(limit: 50, cursor: 'next_page_token')
270
+ ```
271
+
272
+ ## Development
273
+
274
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
275
+
276
+ To install this gem onto your local machine, run:
277
+
278
+ ```bash
279
+ bundle exec rake install
280
+ ```
281
+
282
+ To release a new version, update the version number in `lib/bundleup/version.rb`, and then run:
283
+
284
+ ```bash
285
+ bundle exec rake release
286
+ ```
287
+
288
+ ## Testing
289
+
290
+ Run the test suite:
291
+
292
+ ```bash
293
+ bundle exec rspec
294
+ ```
295
+
296
+ ## Contributing
297
+
298
+ Bug reports and pull requests are welcome on GitHub at https://github.com/bundleup/bundleup-sdk-ruby.
299
+
300
+ ## License
301
+
302
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
303
+
304
+ ## Support
305
+
306
+ - Documentation: [https://docs.bundleup.io](https://docs.bundleup.io)
307
+ - Email: [support@bundleup.io](mailto:support@bundleup.io)
308
+ - GitHub Issues: [https://github.com/bundleup/bundleup-sdk-ruby/issues](https://github.com/bundleup/bundleup-sdk-ruby/issues)
309
+
310
+ ## Code of Conduct
311
+
312
+ Everyone interacting in the BundleUp project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bundleup/bundleup-sdk-ruby/blob/main/CODE_OF_CONDUCT.md).
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Base class for all BundleUp resources
5
+ class Base
6
+ BASE_URL = 'https://api.bundleup.io'
7
+ API_VERSION = 'v1'
8
+
9
+ attr_reader :api_key
10
+
11
+ def initialize(api_key)
12
+ @api_key = api_key
13
+ end
14
+
15
+ # List all resources
16
+ #
17
+ # @param params [Hash] Query parameters
18
+ # @return [Hash] Response with data and pagination info
19
+ def list(params = {})
20
+ request(:get, resource_path, params)
21
+ end
22
+
23
+ # Create a new resource
24
+ #
25
+ # @param data [Hash] Resource data
26
+ # @return [Hash] Created resource
27
+ def create(data)
28
+ request(:post, resource_path, data)
29
+ end
30
+
31
+ # Retrieve a specific resource
32
+ #
33
+ # @param id [String] Resource ID
34
+ # @return [Hash] Resource data
35
+ def retrieve(id)
36
+ request(:get, "#{resource_path}/#{id}")
37
+ end
38
+
39
+ # Update a resource
40
+ #
41
+ # @param id [String] Resource ID
42
+ # @param data [Hash] Updated resource data
43
+ # @return [Hash] Updated resource
44
+ def update(id, data)
45
+ request(:patch, "#{resource_path}/#{id}", data)
46
+ end
47
+
48
+ # Delete a resource
49
+ #
50
+ # @param id [String] Resource ID
51
+ # @return [Hash] Deletion response
52
+ def delete(id)
53
+ request(:delete, "#{resource_path}/#{id}")
54
+ end
55
+
56
+ private
57
+
58
+ def resource_path
59
+ "/#{API_VERSION}/#{resource_name}"
60
+ end
61
+
62
+ def resource_name
63
+ raise NotImplementedError, 'Subclasses must implement resource_name'
64
+ end
65
+
66
+ def request(method, path, body = nil)
67
+ response = connection.public_send(method) do |req|
68
+ req.url path
69
+ req.headers['Content-Type'] = 'application/json'
70
+ req.headers['Authorization'] = "Bearer #{api_key}"
71
+ req.body = body.to_json if body && %i[post patch put].include?(method)
72
+ req.params.update(body) if body && method == :get
73
+ end
74
+
75
+ handle_response(response)
76
+ rescue Faraday::Error => e
77
+ raise APIError, "Request failed: #{e.message}"
78
+ end
79
+
80
+ def connection
81
+ @connection ||= Faraday.new(url: BASE_URL) do |conn|
82
+ conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2
83
+ conn.adapter Faraday.default_adapter
84
+ end
85
+ end
86
+
87
+ def handle_response(response)
88
+ case response.status
89
+ when 200..299
90
+ response.body.empty? ? {} : JSON.parse(response.body)
91
+ when 401
92
+ raise AuthenticationError, 'Invalid API key'
93
+ when 400..499
94
+ error_message = extract_error_message(response)
95
+ raise InvalidRequestError, error_message
96
+ when 500..599
97
+ raise APIError, 'Server error occurred'
98
+ else
99
+ raise APIError, "Unexpected response status: #{response.status}"
100
+ end
101
+ end
102
+
103
+ def extract_error_message(response)
104
+ body = JSON.parse(response.body)
105
+ body['error'] || body['message'] || "Request failed with status #{response.status}"
106
+ rescue JSON::ParserError
107
+ "Request failed with status #{response.status}"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Main client class for interacting with the BundleUp API
5
+ class Client
6
+ attr_reader :api_key
7
+
8
+ # Initialize a new BundleUp client
9
+ #
10
+ # @param api_key [String] Your BundleUp API key
11
+ # @raise [ArgumentError] if api_key is nil or empty
12
+ def initialize(api_key)
13
+ raise ArgumentError, 'API key is required to initialize BundleUp SDK.' if api_key.nil? || api_key.to_s.empty?
14
+
15
+ @api_key = api_key
16
+ end
17
+
18
+ # Access the Connections resource
19
+ #
20
+ # @return [Bundleup::Connection] Connection resource instance
21
+ def connections
22
+ @connections ||= Bundleup::Connection.new(@api_key)
23
+ end
24
+
25
+ # Access the Integrations resource
26
+ #
27
+ # @return [Bundleup::Integration] Integration resource instance
28
+ def integrations
29
+ @integrations ||= Bundleup::Integration.new(@api_key)
30
+ end
31
+
32
+ # Access the Webhooks resource
33
+ #
34
+ # @return [Bundleup::Webhook] Webhook resource instance
35
+ def webhooks
36
+ @webhooks ||= Bundleup::Webhook.new(@api_key)
37
+ end
38
+
39
+ # Create a Proxy instance for a specific connection
40
+ #
41
+ # @param connection_id [String] The connection ID
42
+ # @return [Bundleup::Proxy] Proxy instance
43
+ # @raise [ArgumentError] if connection_id is nil or empty
44
+ def proxy(connection_id)
45
+ if connection_id.nil? || connection_id.to_s.empty?
46
+ raise ArgumentError,
47
+ 'Connection ID is required to create a Proxy instance.'
48
+ end
49
+
50
+ Bundleup::Proxy.new(@api_key, connection_id)
51
+ end
52
+
53
+ # Create Unify instances for a specific connection
54
+ #
55
+ # @param connection_id [String] The connection ID
56
+ # @return [Hash] Hash with :chat, :git, and :pm Unify instances
57
+ # @raise [ArgumentError] if connection_id is nil or empty
58
+ def unify(connection_id)
59
+ if connection_id.nil? || connection_id.to_s.empty?
60
+ raise ArgumentError,
61
+ 'Connection ID is required to create a Unify instance.'
62
+ end
63
+
64
+ {
65
+ chat: Bundleup::Unify::Chat.new(@api_key, connection_id),
66
+ git: Bundleup::Unify::Git.new(@api_key, connection_id),
67
+ pm: Bundleup::Unify::PM.new(@api_key, connection_id)
68
+ }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Connection resource for managing connections
5
+ class Connection < Base
6
+ private
7
+
8
+ def resource_name
9
+ 'connections'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Base error class for all BundleUp errors
5
+ class Error < StandardError; end
6
+
7
+ # Raised when an API error occurs
8
+ class APIError < Error; end
9
+
10
+ # Raised when authentication fails
11
+ class AuthenticationError < Error; end
12
+
13
+ # Raised when a request is invalid
14
+ class InvalidRequestError < Error; end
15
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Integration resource for managing integrations
5
+ class Integration < Base
6
+ private
7
+
8
+ def resource_name
9
+ 'integrations'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Proxy class for making direct calls to integration APIs
5
+ class Proxy
6
+ BASE_URL = 'https://proxy.bundleup.io'
7
+
8
+ attr_reader :api_key, :connection_id
9
+
10
+ # Initialize a new Proxy instance
11
+ #
12
+ # @param api_key [String] Your BundleUp API key
13
+ # @param connection_id [String] The connection ID
14
+ def initialize(api_key, connection_id)
15
+ @api_key = api_key
16
+ @connection_id = connection_id
17
+ end
18
+
19
+ # Make a GET request
20
+ #
21
+ # @param path [String] The request path
22
+ # @param params [Hash] Query parameters
23
+ # @return [Hash] Response data
24
+ def get(path, params = {})
25
+ request(:get, path, params)
26
+ end
27
+
28
+ # Make a POST request
29
+ #
30
+ # @param path [String] The request path
31
+ # @param body [Hash] Request body
32
+ # @return [Hash] Response data
33
+ def post(path, body = {})
34
+ request(:post, path, body)
35
+ end
36
+
37
+ # Make a PUT request
38
+ #
39
+ # @param path [String] The request path
40
+ # @param body [Hash] Request body
41
+ # @return [Hash] Response data
42
+ def put(path, body = {})
43
+ request(:put, path, body)
44
+ end
45
+
46
+ # Make a PATCH request
47
+ #
48
+ # @param path [String] The request path
49
+ # @param body [Hash] Request body
50
+ # @return [Hash] Response data
51
+ def patch(path, body = {})
52
+ request(:patch, path, body)
53
+ end
54
+
55
+ # Make a DELETE request
56
+ #
57
+ # @param path [String] The request path
58
+ # @param params [Hash] Query parameters
59
+ # @return [Hash] Response data
60
+ def delete(path, params = {})
61
+ request(:delete, path, params)
62
+ end
63
+
64
+ private
65
+
66
+ def request(method, path, body = nil)
67
+ response = connection.public_send(method) do |req|
68
+ req.url path
69
+ req.headers['Content-Type'] = 'application/json'
70
+ req.headers['Authorization'] = "Bearer #{api_key}"
71
+ req.headers['BU-Connection-Id'] = connection_id
72
+
73
+ if body && %i[post patch put].include?(method)
74
+ req.body = body.to_json
75
+ elsif body && method == :get
76
+ req.params.update(body)
77
+ end
78
+ end
79
+
80
+ handle_response(response)
81
+ rescue Faraday::Error => e
82
+ raise APIError, "Proxy request failed: #{e.message}"
83
+ end
84
+
85
+ def connection
86
+ @connection ||= Faraday.new(url: BASE_URL) do |conn|
87
+ conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2
88
+ conn.adapter Faraday.default_adapter
89
+ end
90
+ end
91
+
92
+ def handle_response(response)
93
+ case response.status
94
+ when 200..299
95
+ response.body.empty? ? {} : JSON.parse(response.body)
96
+ when 401
97
+ raise AuthenticationError, 'Invalid API key'
98
+ when 400..499
99
+ error_message = extract_error_message(response)
100
+ raise InvalidRequestError, error_message
101
+ when 500..599
102
+ raise APIError, 'Server error occurred'
103
+ else
104
+ raise APIError, "Unexpected response status: #{response.status}"
105
+ end
106
+ end
107
+
108
+ def extract_error_message(response)
109
+ body = JSON.parse(response.body)
110
+ body['error'] || body['message'] || "Request failed with status #{response.status}"
111
+ rescue JSON::ParserError
112
+ "Request failed with status #{response.status}"
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Unify
5
+ # Base class for all Unify resources
6
+ class Base
7
+ BASE_URL = 'https://unify.bundleup.io'
8
+ API_VERSION = 'v1'
9
+
10
+ attr_reader :api_key, :connection_id
11
+
12
+ # Initialize a new Unify resource
13
+ #
14
+ # @param api_key [String] Your BundleUp API key
15
+ # @param connection_id [String] The connection ID
16
+ def initialize(api_key, connection_id)
17
+ @api_key = api_key
18
+ @connection_id = connection_id
19
+ end
20
+
21
+ private
22
+
23
+ def request(method, path, params = {}, include_raw: false)
24
+ response = connection.public_send(method) do |req|
25
+ req.url path
26
+ req.headers['Content-Type'] = 'application/json'
27
+ req.headers['Authorization'] = "Bearer #{api_key}"
28
+ req.headers['BU-Connection-Id'] = connection_id
29
+ req.headers['BU-Include-Raw'] = include_raw.to_s
30
+
31
+ if params && %i[post patch put].include?(method)
32
+ req.body = params.to_json
33
+ elsif params && method == :get
34
+ req.params.update(params)
35
+ end
36
+ end
37
+
38
+ handle_response(response)
39
+ rescue Faraday::Error => e
40
+ raise APIError, "Unify request failed: #{e.message}"
41
+ end
42
+
43
+ def connection
44
+ @connection ||= Faraday.new(url: BASE_URL) do |conn|
45
+ conn.request :retry, max: 3, interval: 0.5, backoff_factor: 2
46
+ conn.adapter Faraday.default_adapter
47
+ end
48
+ end
49
+
50
+ def handle_response(response)
51
+ case response.status
52
+ when 200..299
53
+ response.body.empty? ? {} : JSON.parse(response.body)
54
+ when 401
55
+ raise AuthenticationError, 'Invalid API key'
56
+ when 400..499
57
+ error_message = extract_error_message(response)
58
+ raise InvalidRequestError, error_message
59
+ when 500..599
60
+ raise APIError, 'Server error occurred'
61
+ else
62
+ raise APIError, "Unexpected response status: #{response.status}"
63
+ end
64
+ end
65
+
66
+ def extract_error_message(response)
67
+ body = JSON.parse(response.body)
68
+ body['error'] || body['message'] || "Request failed with status #{response.status}"
69
+ rescue JSON::ParserError
70
+ "Request failed with status #{response.status}"
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Unify
5
+ # Chat resource for unified chat operations
6
+ class Chat < Base
7
+ # List channels
8
+ #
9
+ # @param params [Hash] Query parameters
10
+ # @option params [Integer] :limit Number of results to return
11
+ # @option params [String] :cursor Pagination cursor
12
+ # @option params [Boolean] :include_raw Include raw response from the integration
13
+ # @return [Hash] Channels data with pagination info
14
+ def channels(params = {})
15
+ include_raw = params.delete(:include_raw) || false
16
+ request(:get, "/#{API_VERSION}/chat/channels", params, include_raw: include_raw)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Unify
5
+ # Git resource for unified git operations
6
+ class Git < Base
7
+ # List repositories
8
+ #
9
+ # @param params [Hash] Query parameters
10
+ # @option params [Integer] :limit Number of results to return
11
+ # @option params [String] :cursor Pagination cursor
12
+ # @option params [Boolean] :include_raw Include raw response from the integration
13
+ # @return [Hash] Repositories data with pagination info
14
+ def repos(params = {})
15
+ include_raw = params.delete(:include_raw) || false
16
+ request(:get, "/#{API_VERSION}/git/repos", params, include_raw: include_raw)
17
+ end
18
+
19
+ # List pull requests
20
+ #
21
+ # @param repo_name [String] Repository name
22
+ # @param params [Hash] Query parameters
23
+ # @option params [Integer] :limit Number of results to return
24
+ # @option params [String] :cursor Pagination cursor
25
+ # @option params [Boolean] :include_raw Include raw response from the integration
26
+ # @return [Hash] Pull requests data with pagination info
27
+ def pulls(repo_name, params = {})
28
+ include_raw = params.delete(:include_raw) || false
29
+ params[:repo_name] = repo_name
30
+ request(:get, "/#{API_VERSION}/git/pulls", params, include_raw: include_raw)
31
+ end
32
+
33
+ # List tags
34
+ #
35
+ # @param repo_name [String] Repository name
36
+ # @param params [Hash] Query parameters
37
+ # @option params [Integer] :limit Number of results to return
38
+ # @option params [String] :cursor Pagination cursor
39
+ # @option params [Boolean] :include_raw Include raw response from the integration
40
+ # @return [Hash] Tags data with pagination info
41
+ def tags(repo_name, params = {})
42
+ include_raw = params.delete(:include_raw) || false
43
+ params[:repo_name] = repo_name
44
+ request(:get, "/#{API_VERSION}/git/tags", params, include_raw: include_raw)
45
+ end
46
+
47
+ # List releases
48
+ #
49
+ # @param repo_name [String] Repository name
50
+ # @param params [Hash] Query parameters
51
+ # @option params [Integer] :limit Number of results to return
52
+ # @option params [String] :cursor Pagination cursor
53
+ # @option params [Boolean] :include_raw Include raw response from the integration
54
+ # @return [Hash] Releases data with pagination info
55
+ def releases(repo_name, params = {})
56
+ include_raw = params.delete(:include_raw) || false
57
+ params[:repo_name] = repo_name
58
+ request(:get, "/#{API_VERSION}/git/releases", params, include_raw: include_raw)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ module Unify
5
+ # PM (Project Management) resource for unified project management operations
6
+ class PM < Base
7
+ # List issues
8
+ #
9
+ # @param params [Hash] Query parameters
10
+ # @option params [Integer] :limit Number of results to return
11
+ # @option params [String] :cursor Pagination cursor
12
+ # @option params [Boolean] :include_raw Include raw response from the integration
13
+ # @return [Hash] Issues data with pagination info
14
+ def issues(params = {})
15
+ include_raw = params.delete(:include_raw) || false
16
+ request(:get, "/#{API_VERSION}/pm/issues", params, include_raw: include_raw)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundleup
4
+ # Webhook resource for managing webhooks
5
+ class Webhook < Base
6
+ private
7
+
8
+ def resource_name
9
+ 'webhooks'
10
+ end
11
+ end
12
+ end
data/lib/bundleup.rb ADDED
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+ require 'faraday/retry'
5
+ require 'json'
6
+
7
+ require_relative 'bundleup/version'
8
+ require_relative 'bundleup/errors'
9
+ require_relative 'bundleup/base'
10
+ require_relative 'bundleup/client'
11
+ require_relative 'bundleup/connection'
12
+ require_relative 'bundleup/integration'
13
+ require_relative 'bundleup/webhook'
14
+ require_relative 'bundleup/proxy'
15
+ require_relative 'bundleup/unify/base'
16
+ require_relative 'bundleup/unify/chat'
17
+ require_relative 'bundleup/unify/git'
18
+ require_relative 'bundleup/unify/pm'
19
+
20
+ # BundleUp Ruby SDK
21
+ #
22
+ # Official Ruby client library for the BundleUp API
23
+ module Bundleup
24
+ class << self
25
+ # Create a new BundleUp client
26
+ #
27
+ # @param api_key [String] Your BundleUp API key
28
+ # @return [Bundleup::Client] A new client instance
29
+ def new(api_key)
30
+ Client.new(api_key)
31
+ end
32
+ end
33
+ end
metadata ADDED
@@ -0,0 +1,161 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bundleup-sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - BundleUp
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-01-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday-retry
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '13.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '13.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.12'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.50'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.50'
83
+ - !ruby/object:Gem::Dependency
84
+ name: vcr
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '6.1'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '6.1'
97
+ - !ruby/object:Gem::Dependency
98
+ name: webmock
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '3.18'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '3.18'
111
+ description: Ruby client library for the BundleUp API
112
+ email:
113
+ - support@bundleup.io
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - CHANGELOG.md
119
+ - LICENSE
120
+ - README.md
121
+ - lib/bundleup.rb
122
+ - lib/bundleup/base.rb
123
+ - lib/bundleup/client.rb
124
+ - lib/bundleup/connection.rb
125
+ - lib/bundleup/errors.rb
126
+ - lib/bundleup/integration.rb
127
+ - lib/bundleup/proxy.rb
128
+ - lib/bundleup/unify/base.rb
129
+ - lib/bundleup/unify/chat.rb
130
+ - lib/bundleup/unify/git.rb
131
+ - lib/bundleup/unify/pm.rb
132
+ - lib/bundleup/version.rb
133
+ - lib/bundleup/webhook.rb
134
+ homepage: https://github.com/bundleup/bundleup-sdk-ruby
135
+ licenses:
136
+ - MIT
137
+ metadata:
138
+ homepage_uri: https://github.com/bundleup/bundleup-sdk-ruby
139
+ source_code_uri: https://github.com/bundleup/bundleup-sdk-ruby
140
+ changelog_uri: https://github.com/bundleup/bundleup-sdk-ruby/blob/main/CHANGELOG.md
141
+ rubygems_mfa_required: 'true'
142
+ post_install_message:
143
+ rdoc_options: []
144
+ require_paths:
145
+ - lib
146
+ required_ruby_version: !ruby/object:Gem::Requirement
147
+ requirements:
148
+ - - ">="
149
+ - !ruby/object:Gem::Version
150
+ version: 2.7.0
151
+ required_rubygems_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - ">="
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ requirements: []
157
+ rubygems_version: 3.5.22
158
+ signing_key:
159
+ specification_version: 4
160
+ summary: Official Ruby SDK for BundleUp
161
+ test_files: []