superset 0.3.5 → 0.4.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b991d014f7094976acde768b112309443a38f741c2b4d2834ecc9575366ad1eb
4
- data.tar.gz: 5701ba1956f94f51037b78f878cb6bb154c74509d4c86d9b0e2b71c83abc1af8
3
+ metadata.gz: 6ec3571934b0c40b84d75e2bfc55fb010fbae561539d65b2a20f6131cfcef323
4
+ data.tar.gz: 9e68d4aa5a36aba31b4b548eb9a7bf131e45c3c168090782207ea9e1238228de
5
5
  SHA512:
6
- metadata.gz: aaa349b9a890fea6b8a45323d80aca1ed9b57d0a71a39defed7430b72ce2ab92a37671b8f91d745c178d55d3e28d2e14b35fbad01c22c38db517ea13cafa94e9
7
- data.tar.gz: 305171e61d4c0ecb3d5cd9b4c7e828f57aa3002e2b089996356c41264046095677c012ed6a147e4baad51aa74110e0189002f7967984b12f6fff22b361e40527
6
+ metadata.gz: 9ac4a4a54a6d41d7094c3e0e4220b1af461066d433a8ddb3199644b243b16e9db177d290dfd48b54beea967f930c5eb677d81ca99b68add155bc688e1e6aa062
7
+ data.tar.gz: d169a9b5b1f85cec5f07e25a54619530923ad1e12ad97f7b5c4f5a73abf5c751f8c83392595f0d0806648c83f59617948523e208ee19dd8307b9105c9375b7c9
data/AGENTS.md ADDED
@@ -0,0 +1,105 @@
1
+ # Agents
2
+
3
+ This file provides guidance to AI coding agents working with code in this repository.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ # Install dependencies
9
+ bundle install
10
+
11
+ # Open interactive console (auto-loads .env)
12
+ bin/console
13
+
14
+ # Run full test suite
15
+ rspec
16
+
17
+ # Run a single test file
18
+ rspec spec/path/to/test_spec.rb
19
+
20
+ # Run a single test by line number
21
+ rspec spec/path/to/test_spec.rb:42
22
+
23
+ # Lint
24
+ rubocop
25
+
26
+ # Run specs + rubocop together
27
+ rake
28
+ ```
29
+
30
+ Docker equivalents: prefix commands with `docker-compose run --rm app`.
31
+
32
+ ### Environment Setup
33
+
34
+ Copy `env.sample` to `.env` and fill in:
35
+ - `SUPERSET_HOST` — API base URL (required)
36
+ - `SUPERSET_API_USERNAME` / `SUPERSET_API_PASSWORD` — API credentials (required)
37
+ - `SUPERSET_ENVIRONMENT` — optional; loads `.env-{ENVIRONMENT}` instead
38
+ - `CONSOLE_TIMEOUT` — seconds before auto-logout in console (default 1800)
39
+
40
+ ## Architecture
41
+
42
+ ### Request Framework
43
+
44
+ All API interactions inherit from `Superset::Request` (`lib/superset/request.rb`). Subclasses implement:
45
+ - `route` — returns the API path (e.g., `"dashboard/#{id}"`)
46
+ - `filters` — returns a filter query string or `""`
47
+ - `list_attributes` — array of field names used for terminal table display
48
+
49
+ **Patterns by HTTP verb:**
50
+ - **GET/LIST** — inherit `Superset::Request`, implement `route` and `filters`
51
+ - **PUT** — inherit `Superset::BasePutRequest`, takes `target_id:` and `params:`, implement `route`
52
+ - **DELETE/POST** — call `client.delete(route)` or `client.post(route, params)` directly
53
+
54
+ Pagination is built into `Superset::Request` (default page size: 100, max: 1000). List classes accept `page_num:` and `page_size:` kwargs.
55
+
56
+ ### Authentication Flow
57
+
58
+ 1. `Superset::Client` (extends `Happi::Client`) includes `Credential::ApiUser` and creates a `Superset::Authenticator`
59
+ 2. `Authenticator` POSTs to `api/v1/security/login` and extracts `access_token`
60
+ 3. `Client#connection` builds a Faraday connection with Bearer token; supports both JSON and multipart modes (`config.use_json`)
61
+
62
+ ### Query Filter Syntax
63
+
64
+ Superset uses an ORM-like filter string format:
65
+ ```
66
+ filters:!((col:column_name,opr:operation,value:value)),
67
+ ```
68
+ Common operations: `ct` (contains), `eq` (equals), `neq` (not equals), `rel_o_m`, `rel_m_m`, `dashboard_tags`.
69
+
70
+ ### Service Layer
71
+
72
+ `lib/superset/services/` contains complex multi-step workflows. The main one is `DuplicateDashboard`, which:
73
+ 1. Validates source/target IDs, schema existence, and data sovereignty rules (all chart datasets must share the same DB schema)
74
+ 2. Copies the dashboard and its charts
75
+ 3. Duplicates datasets pointing to the target schema
76
+ 4. Rewires chart JSON metadata and filter configs to the new dataset IDs
77
+ 5. Optionally sets embedded config, tags, and publishes
78
+ 6. **Rolls back all created objects on failure**
79
+
80
+ `ImportDashboardAcrossEnvironment` handles cross-environment migration via export/import ZIP files.
81
+
82
+ ### Display Mixin
83
+
84
+ `Superset::Display` (`lib/superset/display.rb`) is included in `Superset::Request` and provides `list`, `rows`, `to_h`, and `ids`. Requires `list_attributes` and `result` to be implemented by the subclass.
85
+
86
+ ### Resource–Relationship Model
87
+
88
+ - **Dashboards** contain **Charts**
89
+ - **Charts** reference **Datasets** (datasources)
90
+ - **Dashboard JSON metadata** stores layout, chart positions, and filter configurations
91
+ - **Filters** reference Datasets for filter values
92
+ - Duplication/migration requires updating all these cross-references
93
+
94
+ ### Conventions
95
+
96
+ - Namespace: `Superset::<ResourceType>::<Action>` (e.g., `Superset::Dashboard::List`)
97
+ - `perform` — executes a state-changing action, returns `self`
98
+ - `response` — raw (cached) HTTP response
99
+ - `result` — extracts `response['result']`
100
+ - Constructor filter params follow naming: `title_contains:`, `title_equals:`, etc.
101
+ - Logging goes to `log/superset-client.log` via `Superset::Logger`
102
+
103
+ ### Console Helpers
104
+
105
+ In `bin/console`: `sshelp` (alias: `superset_class_list`) lists all available `Superset::` classes. On startup, `Superset::Database::List.call` prints available DB connections.
data/CHANGELOG.md CHANGED
@@ -1,4 +1,28 @@
1
- ## Change Log
1
+ ## Changelog
2
+
3
+ ## 0.4.0 - 2026-06-05
4
+
5
+ * send X-CSRFToken (and replay the session cookie) on state-changing requests so writes work against a CSRF-protected Superset
6
+ * send a same-origin Referer on state-changing requests to satisfy Flask-WTF WTF_CSRF_SSL_STRICT over HTTPS
7
+ * GuestToken: send X-CSRFToken + Referer (and replay the session cookie) on the guest_token POST — that endpoint is CSRF-protected too, so embedded dashboards broke without it
8
+ * add faraday-cookie_jar dependency
9
+
10
+ ## 0.3.6 - 2026-02-26
11
+
12
+ * add dry_run to dashboard bulk delete cascade #74
13
+ * update to rubyzip 3.2.2 #73
14
+
15
+ ## 0.3.5 - 2026-02-24
16
+
17
+ * allow for empty filter datasets #72
18
+
19
+ ## 0.3.4 - 2026-02-21
20
+
21
+ * cascade ownership should also update datasets for filter only in #68
22
+ * add owner filter #62
23
+ * list dataset catalog db names #69
24
+ * bump version to 0.3.4 #70
25
+ * Adjust asset order for board cascade deletion #71
2
26
 
3
27
  ## 0.3.3 - 2025-12-11
4
28
  * Add databases method to Superset::Dashboard::Datasets::List
data/CLAUDE.md ADDED
@@ -0,0 +1 @@
1
+ See [AGENTS.md](AGENTS.md) for project context and development instructions.
@@ -4,6 +4,8 @@ Superset API Credentials are essentially the username, password and host of the
4
4
 
5
5
  If you know these already, plug them and your host value into the `.env` and it should all just work. A sample env.sample is provided as a template.
6
6
 
7
+ Note: `.env` files are for local development only—do not store production credentials in a `.env` file (or commit it to git). In production, inject secrets at runtime via your platform's environment variables or a secret manager.
8
+
7
9
  Create your own .env file with
8
10
 
9
11
  ```
@@ -59,9 +61,17 @@ Scroll down to the 'Security Users' area and find the PUT request that will upda
59
61
 
60
62
  PUT `/api/v1/security/users/{pk}`
61
63
 
62
- Click 'Try it Out' and add your users ID in the PK input box.
64
+ Click 'Try it Out' and add your users ID in the PK input box.
65
+
66
+ Generate a secure random password with your preferred tool ... or in ruby with something like:
67
+ ```ruby
68
+ # ruby SecureRandom code to provide 32 random bytes
69
+ require 'securerandom'
70
+ SecureRandom.urlsafe_base64(32)
71
+ ```
72
+
73
+ Edit the params to only consist of only the password field and the value of your new password.
63
74
 
64
- Edit the params to only consist of only the password field and the value of your new password.
65
75
 
66
76
  ```
67
77
  {
@@ -159,5 +169,15 @@ Superset::GuestToken.new(embedded_dashboard_id: '15').guest_token
159
169
  => "eyJ0eXAiOi............VV4mrMfsvg"
160
170
  ```
161
171
 
172
+ ## Troubleshooting tips
162
173
 
174
+ ### Password or token expired
175
+
176
+ If your Superset password has expired (or your auth token is no longer valid), API calls may fail with an error like:
177
+
178
+ ```ruby
179
+ Superset::Dashboard::List.call
180
+ ArgumentError: Can't build an Authorization Bearer header from nil
181
+ ```
163
182
 
183
+ Fix: refresh your credentials via swagger interface then update your credentials in .env and try again.
@@ -1,7 +1,14 @@
1
+ require 'faraday-cookie_jar'
2
+
1
3
  module Superset
2
4
  class Client < Happi::Client
3
5
  include Credential::ApiUser
4
6
 
7
+ # Superset enforces CSRF on state-changing requests once WTF_CSRF_ENABLED is on;
8
+ # GETs are never CSRF-checked. Bearer-token auth is not sufficient,
9
+ # so these verbs must carry an X-CSRFToken header (see #call / #csrf_token).
10
+ CSRF_PROTECTED_METHODS = %i[post put patch delete].freeze
11
+
5
12
  attr_reader :authenticator
6
13
 
7
14
  def initialize
@@ -17,6 +24,15 @@ module Superset
17
24
  @superset_host ||= authenticator.superset_host
18
25
  end
19
26
 
27
+ # All verbs funnel through Happi::Client#call. Before any state-changing request,
28
+ # attach a CSRF token; fetching one also sets the Flask session cookie that the
29
+ # token is validated against, which the cookie jar on this connection replays on
30
+ # the write.
31
+ def call(method, url, params = {})
32
+ set_csrf_token if CSRF_PROTECTED_METHODS.include?(method)
33
+ super
34
+ end
35
+
20
36
  # TODO: Happi has not got a put method yet
21
37
  def put(resource, params = {})
22
38
  call(:put, url(resource), param_check(params))
@@ -40,9 +56,27 @@ module Superset
40
56
 
41
57
  private
42
58
 
59
+ # Set the CSRF headers for the upcoming write:
60
+ # * X-CSRFToken — session-bound token; GET /api/v1/security/csrf_token/ returns it
61
+ # and sets the Flask session cookie it is validated against (the cookie jar on
62
+ # this connection replays that cookie on the write).
63
+ # * Referer — over HTTPS, Flask-WTF's WTF_CSRF_SSL_STRICT (default True) also
64
+ # requires a Referer matching the Superset host (same-origin check), else the
65
+ # write fails with "400 The referrer header is missing." Browsers send this
66
+ # automatically; an API client must set it explicitly.
67
+ def set_csrf_token
68
+ connection.headers['X-CSRFToken'] = csrf_token
69
+ connection.headers['Referer'] = superset_host
70
+ end
71
+
72
+ def csrf_token
73
+ @csrf_token ||= get('security/csrf_token/')['result']
74
+ end
75
+
43
76
  def connection
44
77
  @connection ||= Faraday.new(superset_host) do |f|
45
78
  f.authorization :Bearer, access_token
79
+ f.use :cookie_jar # persist the Flask session cookie across the csrf_token GET and the write
46
80
  f.use FaradayMiddleware::ParseJson, content_type: 'application/json'
47
81
 
48
82
  if self.config.use_json
@@ -9,10 +9,11 @@ module Superset
9
9
  class BulkDeleteCascade
10
10
  class InvalidParameterError < StandardError; end
11
11
 
12
- attr_reader :dashboard_ids
12
+ attr_reader :dashboard_ids, :dry_run
13
13
 
14
- def initialize(dashboard_ids: [])
14
+ def initialize(dashboard_ids: [], dry_run: true)
15
15
  @dashboard_ids = dashboard_ids
16
+ @dry_run = dry_run
16
17
  end
17
18
 
18
19
  def perform
@@ -20,9 +21,16 @@ module Superset
20
21
  raise InvalidParameterError, "dashboard_ids array must contain Integer only values" unless dashboard_ids.all? { |item| item.is_a?(Integer) }
21
22
 
22
23
  dashboard_ids.sort.each do |dashboard_id|
23
- logger.info("Dashboard Id: #{dashboard_id.to_s} Attempting CASCADE delete of dashboard, charts, datasets")
24
- delete_charts(dashboard_id)
25
- delete_datasets(dashboard_id)
24
+ chart_ids = retrieve_chart_ids(dashboard_id)
25
+ dataset_ids = retrieve_dataset_ids(dashboard_id)
26
+
27
+ log_msg("------------------------- DRY RUN ONLY ---------------------------") if dry_run
28
+ log_msg("Dashboard Id: #{dashboard_id.to_s} Attempting CASCADE delete of dashboard, charts, datasets")
29
+ log_msg(" Charts: #{chart_ids.sort.join(', ')}")
30
+ log_msg(" Datasets: #{dataset_ids.sort.join(', ')}")
31
+
32
+ delete_charts(chart_ids)
33
+ delete_datasets(dataset_ids)
26
34
  delete_dashboard(dashboard_id)
27
35
  end
28
36
  true
@@ -30,18 +38,29 @@ module Superset
30
38
 
31
39
  private
32
40
 
33
- def delete_datasets(dashboard_id)
34
- datasets_to_delete = Superset::Dashboard::Datasets::List.new(dashboard_id: dashboard_id).datasets_details.map{|d| d[:id] }
35
- Superset::Dataset::BulkDelete.new(dataset_ids: datasets_to_delete).perform if datasets_to_delete.any?
41
+ def delete_datasets(dataset_ids)
42
+ Superset::Dataset::BulkDelete.new(dataset_ids: dataset_ids).perform if dataset_ids.any? && !dry_run
36
43
  end
37
44
 
38
- def delete_charts(dashboard_id)
39
- charts_to_delete = Superset::Dashboard::Charts::List.new(dashboard_id).chart_ids
40
- Superset::Chart::BulkDelete.new(chart_ids: charts_to_delete).perform if charts_to_delete.any?
45
+ def delete_charts(chart_ids)
46
+ Superset::Chart::BulkDelete.new(chart_ids: chart_ids).perform if chart_ids.any? && !dry_run
41
47
  end
42
48
 
43
49
  def delete_dashboard(dashboard_id)
44
- Superset::Dashboard::Delete.new(dashboard_id: dashboard_id, confirm_zero_charts: true).perform
50
+ Superset::Dashboard::Delete.new(dashboard_id: dashboard_id, confirm_zero_charts: true).perform if !dry_run
51
+ end
52
+
53
+ def retrieve_chart_ids(dashboard_id)
54
+ Superset::Dashboard::Charts::List.new(dashboard_id).chart_ids
55
+ end
56
+
57
+ def retrieve_dataset_ids(dashboard_id)
58
+ Superset::Dashboard::Datasets::List.new(dashboard_id: dashboard_id).ids
59
+ end
60
+
61
+ def log_msg(message)
62
+ puts message
63
+ logger.info(message)
45
64
  end
46
65
 
47
66
  def logger
@@ -3,8 +3,12 @@
3
3
  # Will then unzip and copy the files into the destination_path with the dashboard_id as a subfolder
4
4
  #
5
5
  # Usage
6
- # Superset::Dashboard::Export.new(dashboard_id: 15, destination_path: '/tmp/superset_dashboard_backups/').perform
7
- #
6
+ =begin
7
+ Superset::Dashboard::Export.new(
8
+ dashboard_id: 15,
9
+ destination_path: '/superset_dashboard_backups/'
10
+ ).perform
11
+ =end
8
12
 
9
13
  require 'superset/file_utilities'
10
14
 
@@ -13,7 +17,7 @@ module Superset
13
17
  class Export < Request
14
18
  include FileUtilities
15
19
 
16
- TMP_SUPERSET_DASHBOARD_PATH = '/tmp/superset_dashboard_exports'
20
+ TMP_SUPERSET_DASHBOARD_PATH = '/tmp/superset_dashboard_exports'.freeze
17
21
 
18
22
  attr_reader :dashboard_id, :destination_path
19
23
 
@@ -125,21 +129,6 @@ module Superset
125
129
  def datestamp
126
130
  @datestamp ||= Time.now.strftime('%Y%m%d')
127
131
  end
128
-
129
- def unzip_file(zip_path, destination)
130
- extracted_files = []
131
- Zip::File.open(zip_path) do |zip_file|
132
- zip_file.each do |entry|
133
- entry_path = File.join(destination, entry.name)
134
- FileUtils.mkdir_p(File.dirname(entry_path))
135
- zip_file.extract(entry, entry_path) unless File.exist?(entry_path)
136
- extracted_files << entry_path
137
- end
138
- end
139
- extracted_files
140
- rescue => e
141
- raise
142
- end
143
132
  end
144
133
 
145
134
  def logger
@@ -78,7 +78,8 @@ module Superset
78
78
  def source_zip_file
79
79
  return source if zip?
80
80
 
81
- Zip::File.open(new_zip_file, Zip::File::CREATE) do |zipfile|
81
+ require 'zip' # lazy: only import/export needs rubyzip
82
+ Zip::File.open(new_zip_file, create: true) do |zipfile|
82
83
  Dir[File.join(source, "**", "**")].each do |file|
83
84
  next unless File.file?(file)
84
85
 
@@ -16,10 +16,10 @@ module Superset
16
16
  end
17
17
 
18
18
  def perform
19
- raise InvalidParameterError, "dataset_ids array of integers expected" unless dataset_ids.is_a?(Array)
19
+ raise InvalidParameterError, "dataset_ids array of integers expected" unless dataset_ids.is_a?(Array) && dataset_ids.any?
20
20
  raise InvalidParameterError, "dataset_ids array must contain Integer only values" unless dataset_ids.all? { |item| item.is_a?(Integer) }
21
21
 
22
- logger.info("Deleting datasets with id: #{dataset_ids.join(', ')}")
22
+ logger.info("Deleting datasets with id: #{dataset_ids&.sort&.join(',')}")
23
23
  response
24
24
  end
25
25
 
@@ -30,7 +30,8 @@ module Superset
30
30
  private
31
31
 
32
32
  def params
33
- { q: "!(#{dataset_ids.join(',')})" }
33
+ dataset_ids_str = dataset_ids&.sort&.join(',')
34
+ { q: "!(#{dataset_ids_str})" }
34
35
  end
35
36
 
36
37
  def route
@@ -1,16 +1,21 @@
1
- require 'zip'
2
-
3
1
  module Superset
4
2
  module FileUtilities
3
+ # rubyzip is loaded lazily so the gem can be required without it; only
4
+ # consumers that actually import/export need rubyzip installed.
5
5
  def unzip_file(zip_file, destination)
6
+ require 'zip'
6
7
  entries = []
7
8
  Zip::File.open(zip_file) do |zip|
8
9
  zip.each do |entry|
10
+ next if entry.name.empty?
11
+
9
12
  entry_path = File.join(destination, entry.name)
10
13
  entries << entry_path
11
14
  FileUtils.mkdir_p(File.dirname(entry_path))
12
15
 
13
- zip.extract(entry, entry_path) unless File.exist?(entry_path)
16
+ zip.extract(entry, entry.name, destination_directory: destination) { true }
17
+ rescue => e
18
+ raise "Error extracting file #{entry.name}: #{e.message}"
14
19
  end
15
20
  end
16
21
 
@@ -1,3 +1,5 @@
1
+ require 'faraday-cookie_jar'
2
+
1
3
  module Superset
2
4
  class GuestToken
3
5
  include Credential::EmbeddedUser
@@ -52,13 +54,27 @@ module Superset
52
54
  'api/v1/security/guest_token/'
53
55
  end
54
56
 
57
+ # The guest_token endpoint is CSRF-protected (it is NOT in Superset's CSRF
58
+ # exempt list), so this POST needs the same treatment as Client writes:
59
+ # an X-CSRFToken bound to the session cookie, plus a same-origin
60
+ # Referer for WTF_CSRF_SSL_STRICT over HTTPS. The csrf_token GET also sets the
61
+ # session cookie that the cookie jar replays on the POST.
55
62
  def response
56
- @response ||= connection.post(route, params.to_json)
63
+ @response ||= begin
64
+ connection.headers['X-CSRFToken'] = csrf_token
65
+ connection.headers['Referer'] = authenticator.superset_host
66
+ connection.post(route, params.to_json)
67
+ end
68
+ end
69
+
70
+ def csrf_token
71
+ @csrf_token ||= connection.get('api/v1/security/csrf_token/').env.body['result']
57
72
  end
58
73
 
59
74
  def connection
60
75
  @connection ||= Faraday.new(authenticator.superset_host) do |f|
61
76
  f.authorization :Bearer, access_token
77
+ f.use :cookie_jar # replay the Flask session cookie from the csrf_token GET on the POST
62
78
  f.use FaradayMiddleware::ParseJson, content_type: 'application/json'
63
79
  f.request :json
64
80
  f.adapter :net_http
@@ -87,7 +87,8 @@ module Superset
87
87
  end
88
88
 
89
89
  def create_new_dashboard_zip
90
- Zip::File.open(new_zip_file, Zip::File::CREATE) do |zipfile|
90
+ require 'zip' # lazy: only import/export needs rubyzip
91
+ Zip::File.open(new_zip_file, create: true) do |zipfile|
91
92
  Dir[File.join(dashboard_export_root_path, '**', '**')].each do |file|
92
93
  zipfile.add(file.sub(dashboard_export_root_path + '/', File.basename(dashboard_export_root_path) + '/' ), file) if File.file?(file)
93
94
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Superset
4
- VERSION = "0.3.5"
4
+ VERSION = "0.4.0"
5
5
  end
data/superset.gemspec CHANGED
@@ -37,11 +37,17 @@ Gem::Specification.new do |spec|
37
37
  spec.add_dependency "json", ">= 2.0"
38
38
  spec.add_dependency "terminal-table", "~> 4.0"
39
39
  spec.add_dependency "require_all", ">= 3.0"
40
- spec.add_dependency "rubyzip", ">= 1.3"
41
40
  spec.add_dependency "faraday", "~> 1.0"
42
41
  spec.add_dependency "faraday-multipart", "~> 1.0"
42
+ spec.add_dependency "faraday-cookie_jar", "~> 0.0.7" # replay the Flask session cookie for CSRF
43
43
  spec.add_dependency "enumerate_it", ">= 1.7"
44
44
 
45
+ # rubyzip is only needed by the dashboard import/export feature, which lazily
46
+ # `require 'zip'` at call time. Kept as a dev dependency so those specs run here,
47
+ # but NOT a runtime dependency — consumers that don't import/export (embedders,
48
+ # read/write API clients) shouldn't inherit its rubyzip >= 3 pin.
49
+ # Apps that DO use import/export must declare rubyzip (>= 3.0) themselves.
50
+ spec.add_development_dependency "rubyzip", ">= 3.0"
45
51
  spec.add_development_dependency "dotenv", ">= 2.0"
46
52
  spec.add_development_dependency "rake", ">= 13.0"
47
53
  spec.add_development_dependency "rspec", ">= 3.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: superset
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - jbat
@@ -52,21 +52,21 @@ dependencies:
52
52
  - !ruby/object:Gem::Version
53
53
  version: '3.0'
54
54
  - !ruby/object:Gem::Dependency
55
- name: rubyzip
55
+ name: faraday
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - ">="
58
+ - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '1.3'
60
+ version: '1.0'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
- - - ">="
65
+ - - "~>"
66
66
  - !ruby/object:Gem::Version
67
- version: '1.3'
67
+ version: '1.0'
68
68
  - !ruby/object:Gem::Dependency
69
- name: faraday
69
+ name: faraday-multipart
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - "~>"
@@ -80,19 +80,19 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '1.0'
82
82
  - !ruby/object:Gem::Dependency
83
- name: faraday-multipart
83
+ name: faraday-cookie_jar
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - "~>"
87
87
  - !ruby/object:Gem::Version
88
- version: '1.0'
88
+ version: 0.0.7
89
89
  type: :runtime
90
90
  prerelease: false
91
91
  version_requirements: !ruby/object:Gem::Requirement
92
92
  requirements:
93
93
  - - "~>"
94
94
  - !ruby/object:Gem::Version
95
- version: '1.0'
95
+ version: 0.0.7
96
96
  - !ruby/object:Gem::Dependency
97
97
  name: enumerate_it
98
98
  requirement: !ruby/object:Gem::Requirement
@@ -107,6 +107,20 @@ dependencies:
107
107
  - - ">="
108
108
  - !ruby/object:Gem::Version
109
109
  version: '1.7'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rubyzip
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '3.0'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '3.0'
110
124
  - !ruby/object:Gem::Dependency
111
125
  name: dotenv
112
126
  requirement: !ruby/object:Gem::Requirement
@@ -201,7 +215,9 @@ files:
201
215
  - ".rspec"
202
216
  - ".rubocop.yml"
203
217
  - ".ruby-version"
218
+ - AGENTS.md
204
219
  - CHANGELOG.md
220
+ - CLAUDE.md
205
221
  - Dockerfile
206
222
  - LICENSE
207
223
  - README.md