basecamp-sdk 0.6.0 → 0.7.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.
data/lib/basecamp/http.rb CHANGED
@@ -80,6 +80,17 @@ module Basecamp
80
80
  single_request_raw(:post, url, body: body, content_type: content_type, attempt: 1)
81
81
  end
82
82
 
83
+ # Performs a PUT request with raw binary data.
84
+ # Used for multipart uploads (e.g., account logo).
85
+ # @param path [String] URL path
86
+ # @param body [String, IO] raw binary data
87
+ # @param content_type [String] MIME content type
88
+ # @return [Response]
89
+ def put_raw(path, body:, content_type:)
90
+ url = build_url(path)
91
+ single_request_raw(:put, url, body: body, content_type: content_type, attempt: 1)
92
+ end
93
+
83
94
  # Performs a GET request without retry logic.
84
95
  # Used for the download flow where retry is not appropriate.
85
96
  # @param url [String] absolute URL
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Basecamp
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  API_VERSION = "2026-01-26"
6
6
  end
@@ -13,6 +13,7 @@
13
13
 
14
14
  require 'json'
15
15
  require 'fileutils'
16
+ require 'set'
16
17
 
17
18
  # Service generator for Ruby SDK
18
19
  class ServiceGenerator
@@ -117,6 +118,7 @@ class ServiceGenerator
117
118
  'Todos' => %w[ListTodos CreateTodo GetTodo UpdateTodo CompleteTodo UncompleteTodo TrashTodo],
118
119
  'Todolists' => %w[GetTodolistOrGroup UpdateTodolistOrGroup ListTodolists CreateTodolist],
119
120
  'Todosets' => %w[GetTodoset],
121
+ 'HillCharts' => %w[GetHillChart UpdateHillChartSettings],
120
122
  'TodolistGroups' => %w[ListTodolistGroups CreateTodolistGroup RepositionTodolistGroup]
121
123
  },
122
124
  'Untagged' => {
@@ -229,7 +231,9 @@ class ServiceGenerator
229
231
  'UpdateScheduleEntry' => 'update_entry',
230
232
  'CreateScheduleEntry' => 'create_entry',
231
233
  'ListScheduleEntries' => 'list_entries',
232
- 'GetScheduleEntryOccurrence' => 'get_entry_occurrence'
234
+ 'GetScheduleEntryOccurrence' => 'get_entry_occurrence',
235
+ 'GetHillChart' => 'get',
236
+ 'UpdateHillChartSettings' => 'update_settings'
233
237
  }.freeze
234
238
 
235
239
  # Verb patterns for extracting method names
@@ -270,7 +274,7 @@ class ServiceGenerator
270
274
  lineupmarker clientapproval clientapprovals clientcorrespondence
271
275
  clientcorrespondences clientreply clientreplies forwardreply
272
276
  forwardreplies campfireline campfirelines todolistgroup todolistgroups
273
- todolistorgroup uploadversions
277
+ todolistorgroup uploadversions hillchart hillcharts
274
278
  ].freeze
275
279
 
276
280
  def initialize(openapi_path)
@@ -293,6 +297,18 @@ class ServiceGenerator
293
297
  puts "Generated #{filename} (#{service[:operations].length} operations)"
294
298
  end
295
299
 
300
+ # Remove stale generated files that are no longer produced by the current spec.
301
+ # Only deletes files with the @generated marker to avoid removing hand-written files.
302
+ generated_set = generated_files.to_set
303
+ Dir.glob(File.join(output_dir, '*_service.rb')).each do |existing|
304
+ basename = File.basename(existing)
305
+ next if generated_set.include?(basename)
306
+ next unless File.read(existing, 256).include?('@generated')
307
+
308
+ File.delete(existing)
309
+ puts "Removed stale #{basename}"
310
+ end
311
+
296
312
  puts "\nGenerated #{services.length} services with #{services.values.sum { |s| s[:operations].length }} operations total."
297
313
  generated_files
298
314
  end
@@ -363,6 +379,8 @@ class ServiceGenerator
363
379
  # Check for request body (JSON or binary)
364
380
  body_schema_ref = operation.dig('requestBody', 'content', 'application/json', 'schema')
365
381
  has_binary_body = operation.dig('requestBody', 'content', 'application/octet-stream', 'schema')
382
+ has_multipart_body = operation.dig('requestBody', 'content', 'multipart/form-data', 'schema')
383
+ multipart_field = operation.dig('x-basecamp-multipart', 'field')
366
384
 
367
385
  # Extract body parameters from schema
368
386
  body_params = extract_body_params(body_schema_ref)
@@ -384,6 +402,8 @@ class ServiceGenerator
384
402
  body_params: body_params,
385
403
  has_body: body_params.any?,
386
404
  has_binary_body: !!has_binary_body,
405
+ has_multipart_body: !!has_multipart_body,
406
+ multipart_field: multipart_field,
387
407
  returns_void: returns_void,
388
408
  returns_array: returns_array,
389
409
  is_mutation: http_method != 'GET',
@@ -553,6 +573,13 @@ class ServiceGenerator
553
573
  lines << ' # @param content_type [String] MIME type of the file (e.g., "application/pdf", "image/png")'
554
574
  end
555
575
 
576
+ # Add @param tags for multipart upload params
577
+ if op[:has_multipart_body]
578
+ lines << ' # @param io [IO, String] File data to upload (IO object or string)'
579
+ lines << ' # @param filename [String] Display name for the uploaded file'
580
+ lines << ' # @param content_type [String] MIME type of the file (e.g., "image/png")'
581
+ end
582
+
556
583
  # Add @param tags for body params
557
584
  if op[:body_params]&.any?
558
585
  op[:body_params].each do |b|
@@ -648,7 +675,11 @@ class ServiceGenerator
648
675
  end
649
676
 
650
677
  # Binary upload parameters
651
- if op[:has_binary_body]
678
+ if op[:has_multipart_body]
679
+ params << 'io:'
680
+ params << 'filename:'
681
+ params << 'content_type:'
682
+ elsif op[:has_binary_body]
652
683
  params << 'data:'
653
684
  params << 'content_type:'
654
685
  elsif op[:has_body]
@@ -703,7 +734,11 @@ class ServiceGenerator
703
734
  lines = []
704
735
  http_method = op[:http_method].downcase
705
736
 
706
- if op[:has_body]
737
+ if op[:has_multipart_body]
738
+ field = op[:multipart_field] || 'file'
739
+ lines << " http_#{http_method}_multipart(#{path_expr}, io: io, filename: filename, " \
740
+ "content_type: content_type, field: \"#{field}\")"
741
+ elsif op[:has_body]
707
742
  body_expr = build_body_expression(op)
708
743
  lines << " http_#{http_method}(#{path_expr}, body: #{body_expr})"
709
744
  else
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: basecamp-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Basecamp
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-16 00:00:00.000000000 Z
11
+ date: 2026-03-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -192,6 +192,7 @@ files:
192
192
  - lib/basecamp/exit_code.rb
193
193
  - lib/basecamp/forbidden_error.rb
194
194
  - lib/basecamp/generated/metadata.json
195
+ - lib/basecamp/generated/services/account_service.rb
195
196
  - lib/basecamp/generated/services/attachments_service.rb
196
197
  - lib/basecamp/generated/services/authorization_service.rb
197
198
  - lib/basecamp/generated/services/automation_service.rb
@@ -211,10 +212,14 @@ files:
211
212
  - lib/basecamp/generated/services/documents_service.rb
212
213
  - lib/basecamp/generated/services/events_service.rb
213
214
  - lib/basecamp/generated/services/forwards_service.rb
215
+ - lib/basecamp/generated/services/gauges_service.rb
216
+ - lib/basecamp/generated/services/hill_charts_service.rb
214
217
  - lib/basecamp/generated/services/lineup_service.rb
215
218
  - lib/basecamp/generated/services/message_boards_service.rb
216
219
  - lib/basecamp/generated/services/message_types_service.rb
217
220
  - lib/basecamp/generated/services/messages_service.rb
221
+ - lib/basecamp/generated/services/my_assignments_service.rb
222
+ - lib/basecamp/generated/services/my_notifications_service.rb
218
223
  - lib/basecamp/generated/services/people_service.rb
219
224
  - lib/basecamp/generated/services/projects_service.rb
220
225
  - lib/basecamp/generated/services/recordings_service.rb