stackshift-assets 0.1.1
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 +7 -0
- data/README.md +3 -0
- data/lib/stackshift/assets.rb +4 -0
- data/lib/stackshift/assets_client.rb +374 -0
- data/lib/stackshift/rails.rb +15 -0
- metadata +46 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 807ebbf9dcca45f0527128331eacd65c6438581f68aba83fc00ee8353f1436e6
|
|
4
|
+
data.tar.gz: 801b1dfeb2e91fcd06718a61360d723d181ac7a4b7ef7b121909669273b38345
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 6f54bb7b1538c96213db8d49c56074f3c930edb154db01e181eacfcfc52a9af8883e0ea088dff7e086363155c57e02d54664539d1a6321c0373a8cc0b18101e4
|
|
7
|
+
data.tar.gz: bf63657edece479337ddd300ed942814a3ae10a1451b704605e50276350ac58c5c684f594ca10b5e692772e8e3932b69f6d8e304259b3fe48d41d88c68048e3c
|
data/README.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "digest"
|
|
5
|
+
require "net/http"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "uri"
|
|
8
|
+
|
|
9
|
+
module StackShift
|
|
10
|
+
class AssetsClient
|
|
11
|
+
def initialize(api_key:, base_url: "https://api.stackshift.cloud/api/v1", cdn_base_url: "https://cdn.stackshift.cloud")
|
|
12
|
+
raise ArgumentError, "StackShift SDK requires an API key" if api_key.to_s.strip.empty?
|
|
13
|
+
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@base_url = base_url.sub(%r{/$}, "")
|
|
16
|
+
@cdn_base_url = cdn_base_url.sub(%r{/$}, "")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def list(**filters)
|
|
20
|
+
request(:get, "/assets", query: compact(filters))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get(asset_id)
|
|
24
|
+
request(:get, "/assets/#{escape(asset_id)}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def delete(asset_id, revision:, idempotency_key: nil)
|
|
28
|
+
headers = if_match(revision)
|
|
29
|
+
headers["Idempotency-Key"] = idempotency_key if idempotency_key
|
|
30
|
+
request(:delete, "/assets/#{escape(asset_id)}", headers: headers)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def upload(file_path, **options)
|
|
34
|
+
multipart(:post, "/assets/upload", file_path, options)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def replace(asset_id, file_path, revision:, **options)
|
|
38
|
+
multipart(:put, "/assets/#{escape(asset_id)}/replace", file_path, options, headers: if_match(revision))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def create_upload_session(idempotency_key: nil, **options)
|
|
42
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : {}
|
|
43
|
+
request(:post, "/assets/upload-sessions", body: compact(options), headers: headers)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def signed_upload_url(idempotency_key: nil, **options)
|
|
47
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : {}
|
|
48
|
+
request(:post, "/assets/upload-url", body: compact(options), headers: headers)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def upload_chunk(upload_url_or_token, part_number, bytes)
|
|
52
|
+
target = if upload_url_or_token.start_with?("http")
|
|
53
|
+
upload_url_or_token.sub(%r{/$}, "")
|
|
54
|
+
else
|
|
55
|
+
@base_url.sub(%r{/api/v1$}, "") + "/uploads/#{escape(upload_url_or_token)}"
|
|
56
|
+
end
|
|
57
|
+
request_absolute(:put, "#{target}/parts/#{part_number}", body: bytes, json: false, authenticated: false,
|
|
58
|
+
headers: { "Content-Type" => "application/octet-stream", "X-Content-SHA256" => Digest::SHA256.hexdigest(bytes) })
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def complete_upload_session(session_id)
|
|
62
|
+
request(:post, "/assets/upload-sessions/#{escape(session_id)}/complete")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def resume_upload_session(session_id)
|
|
66
|
+
request(:get, "/assets/upload-sessions/#{escape(session_id)}")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def cancel_upload_session(session_id)
|
|
70
|
+
request(:delete, "/assets/upload-sessions/#{escape(session_id)}")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def signed_url(asset_id, expires_in: "10m", max_downloads: nil)
|
|
74
|
+
request(:post, "/assets/#{escape(asset_id)}/signed-url",
|
|
75
|
+
body: compact(expires_in: expires_in, max_downloads: max_downloads))
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def list_buckets
|
|
79
|
+
request(:get, "/assets/buckets")
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_bucket(**policy)
|
|
83
|
+
request(:post, "/assets/buckets", body: compact(policy))
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def update_bucket(bucket_id, revision:, **policy)
|
|
87
|
+
request(:put, "/assets/buckets/#{escape(bucket_id)}", body: compact(policy), headers: if_match(revision))
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def delete_bucket(bucket_id, revision:)
|
|
91
|
+
request(:delete, "/assets/buckets/#{escape(bucket_id)}", headers: if_match(revision))
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def patch(asset_id, revision:, **changes)
|
|
95
|
+
request(:patch, "/assets/#{escape(asset_id)}", body: compact(changes), headers: if_match(revision))
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set_tags(asset_id, tags, revision:)
|
|
99
|
+
request(:put, "/assets/#{escape(asset_id)}/tags", body: { tags: tags }, headers: if_match(revision))
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def patch_metadata(asset_id, metadata, revision:)
|
|
103
|
+
request(:patch, "/assets/#{escape(asset_id)}/metadata", body: { metadata: metadata }, headers: if_match(revision))
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def get_ai_config
|
|
107
|
+
request(:get, "/assets/ai/config")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def get_policy
|
|
111
|
+
request(:get, "/assets/policy")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def update_policy(**policy)
|
|
115
|
+
request(:put, "/assets/policy", body: compact(policy))
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def list_transformations
|
|
119
|
+
request(:get, "/assets/transformations")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def create_transformation(name, **options)
|
|
123
|
+
request(:post, "/assets/transformations", body: compact(options.merge(name: name)))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def delete_transformation(name)
|
|
127
|
+
request(:delete, "/assets/transformations/#{escape(name)}")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def signed_transform_url(asset_id, **options)
|
|
131
|
+
request(:post, "/assets/#{escape(asset_id)}/transform-url", body: compact(options))
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def update_ai_config(revision:, enabled_actions:, monthly_spend_cap_micros:)
|
|
135
|
+
request(:put, "/assets/ai/config", body: {
|
|
136
|
+
enabled_actions: enabled_actions, monthly_spend_cap_micros: monthly_spend_cap_micros
|
|
137
|
+
}, headers: if_match(revision))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def job(job_id)
|
|
141
|
+
request(:get, "/assets/jobs/#{escape(job_id)}")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def cancel_job(job_id)
|
|
145
|
+
request(:post, "/assets/jobs/#{escape(job_id)}/cancel")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def analyze(asset_id, idempotency_key: nil)
|
|
149
|
+
asset_job("/assets/#{escape(asset_id)}/ai/analyze", idempotency_key)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def ai(asset_id)
|
|
153
|
+
request(:get, "/assets/#{escape(asset_id)}/ai")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def moderate(asset_id, idempotency_key: nil)
|
|
157
|
+
asset_job("/assets/#{escape(asset_id)}/moderation", idempotency_key)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def transcript(asset_id)
|
|
161
|
+
request(:get, "/assets/#{escape(asset_id)}/transcript")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def request_transcript(asset_id, idempotency_key: nil)
|
|
165
|
+
asset_job("/assets/#{escape(asset_id)}/transcript", idempotency_key)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def request_smart_crop(asset_id, idempotency_key: nil)
|
|
169
|
+
asset_job("/assets/#{escape(asset_id)}/smart-crop", idempotency_key)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def request_background_removal(asset_id, idempotency_key: nil)
|
|
173
|
+
asset_job("/assets/#{escape(asset_id)}/background-remove", idempotency_key)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def create_lifecycle_rule(**rule)
|
|
177
|
+
request(:post, "/assets/lifecycle-rules", body: compact(rule))
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def list_lifecycle_rules
|
|
181
|
+
request(:get, "/assets/lifecycle-rules")
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def delete_lifecycle_rule(rule_id)
|
|
185
|
+
request(:delete, "/assets/lifecycle-rules/#{escape(rule_id)}")
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def create_domain(domain)
|
|
189
|
+
request(:post, "/assets/domains", body: { domain: domain })
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def list_domains
|
|
193
|
+
request(:get, "/assets/domains")
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def verify_domain(domain_id)
|
|
197
|
+
request(:post, "/assets/domains/#{escape(domain_id)}/verify")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def delete_domain(domain_id)
|
|
201
|
+
request(:delete, "/assets/domains/#{escape(domain_id)}")
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def analytics
|
|
205
|
+
request(:get, "/assets/analytics")
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def usage_summary
|
|
209
|
+
request(:get, "/assets/summary")
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def events(limit: 50)
|
|
213
|
+
request(:get, "/assets/events", query: { limit: limit })
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def create_webhook(url, event_types: ['asset.*'])
|
|
217
|
+
request(:post, '/assets/webhooks', body: { url: url, event_types: event_types })
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def list_webhooks
|
|
221
|
+
request(:get, '/assets/webhooks')
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def delete_webhook(webhook_id)
|
|
225
|
+
request(:delete, "/assets/webhooks/#{escape(webhook_id)}")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def list_webhook_deliveries(webhook_id, limit: 50)
|
|
229
|
+
request(:get, "/assets/webhooks/#{escape(webhook_id)}/deliveries", query: { limit: limit })
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def retry_webhook_delivery(delivery_id)
|
|
233
|
+
request(:post, "/assets/webhook-deliveries/#{escape(delivery_id)}/retry")
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def purge(asset_id)
|
|
237
|
+
request(:post, "/assets/#{escape(asset_id)}/purge")
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def bulk(action:, items:, idempotency_key: nil, **options)
|
|
241
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : {}
|
|
242
|
+
request(:post, "/assets/bulk", body: options.merge(action: action, items: items), headers: headers)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def versions(asset_id)
|
|
246
|
+
request(:get, "/assets/#{escape(asset_id)}/versions")
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def create_branch(asset_id, name, from_version_id: nil)
|
|
250
|
+
request(:post, "/assets/#{escape(asset_id)}/branches", body: compact(name: name, from_version_id: from_version_id))
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def restore_version(asset_id, version_id)
|
|
254
|
+
request(:post, "/assets/#{escape(asset_id)}/versions/#{escape(version_id)}/restore")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def promote_branch(asset_id, branch)
|
|
258
|
+
request(:post, "/assets/#{escape(asset_id)}/branches/#{escape(branch)}/promote")
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def collections
|
|
262
|
+
request(:get, "/assets/collections")
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def create_collection(**collection)
|
|
266
|
+
request(:post, "/assets/collections", body: compact(collection))
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def update_collection(collection_id, **collection)
|
|
270
|
+
request(:put, "/assets/collections/#{escape(collection_id)}", body: compact(collection))
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def delete_collection(collection_id)
|
|
274
|
+
request(:delete, "/assets/collections/#{escape(collection_id)}")
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def saved_searches
|
|
278
|
+
request(:get, "/assets/saved-searches")
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
def create_saved_search(**search)
|
|
282
|
+
request(:post, "/assets/saved-searches", body: compact(search))
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def update_saved_search(search_id, **search)
|
|
286
|
+
request(:put, "/assets/saved-searches/#{escape(search_id)}", body: compact(search))
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
def delete_saved_search(search_id)
|
|
290
|
+
request(:delete, "/assets/saved-searches/#{escape(search_id)}")
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def immutable_version_url(asset_id, version_id, filename = "asset")
|
|
294
|
+
"#{@cdn_base_url}/assets/#{escape(asset_id)}/versions/#{escape(version_id)}/#{escape(filename)}"
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def version_url(asset_id, version_id, token: nil)
|
|
298
|
+
prefix = token ? "/private/assets" : "/assets"
|
|
299
|
+
url = "#{@cdn_base_url}#{prefix}/#{escape(asset_id)}/versions/#{escape(version_id)}"
|
|
300
|
+
token ? "#{url}?token=#{escape(token)}" : url
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def branch_url(asset_id, branch, token: nil)
|
|
304
|
+
prefix = token ? "/private/assets" : "/assets"
|
|
305
|
+
url = "#{@cdn_base_url}#{prefix}/#{escape(asset_id)}/branches/#{escape(branch)}"
|
|
306
|
+
token ? "#{url}?token=#{escape(token)}" : url
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
def video_url(asset_id, kind, profile = "default", token: nil)
|
|
310
|
+
prefix = token ? "/private/assets" : "/assets"
|
|
311
|
+
url = "#{@cdn_base_url}#{prefix}/#{escape(asset_id)}/video/#{escape(kind)}/#{escape(profile)}"
|
|
312
|
+
token ? "#{url}?token=#{escape(token)}" : url
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
private
|
|
316
|
+
|
|
317
|
+
def multipart(method, path, file_path, fields, headers: {})
|
|
318
|
+
raise ArgumentError, "Asset upload path is not a file" unless File.file?(file_path)
|
|
319
|
+
|
|
320
|
+
boundary = "stackshift-#{SecureRandom.hex(16)}"
|
|
321
|
+
body = String.new(encoding: Encoding::BINARY)
|
|
322
|
+
compact(fields).each do |name, value|
|
|
323
|
+
serialized = value.is_a?(Hash) || value.is_a?(Array) ? JSON.generate(value) : value.to_s
|
|
324
|
+
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"#{name}\"\r\n\r\n#{serialized}\r\n"
|
|
325
|
+
end
|
|
326
|
+
filename = File.basename(file_path).gsub(/[\"\r\n]/, "_")
|
|
327
|
+
body << "--#{boundary}\r\nContent-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
|
|
328
|
+
body << "Content-Type: application/octet-stream\r\n\r\n" << File.binread(file_path) << "\r\n--#{boundary}--\r\n"
|
|
329
|
+
request_absolute(method, "#{@base_url}#{path}", body: body,
|
|
330
|
+
headers: headers.merge("Content-Type" => "multipart/form-data; boundary=#{boundary}"), json: false, authenticated: true)
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def asset_job(path, idempotency_key)
|
|
334
|
+
headers = idempotency_key ? { "Idempotency-Key" => idempotency_key } : {}
|
|
335
|
+
request(:post, path, headers: headers)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def request(method, path, body: nil, query: nil, headers: {})
|
|
339
|
+
uri = URI("#{@base_url}#{path}")
|
|
340
|
+
uri.query = URI.encode_www_form(query) if query && !query.empty?
|
|
341
|
+
request_absolute(method, uri.to_s, body: body, headers: headers, json: true, authenticated: true)
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def request_absolute(method, url, body:, headers:, json:, authenticated:)
|
|
345
|
+
uri = URI(url)
|
|
346
|
+
klass = Net::HTTP.const_get(method.to_s.capitalize)
|
|
347
|
+
outgoing = klass.new(uri)
|
|
348
|
+
outgoing["Authorization"] = "Bearer #{@api_key}" if authenticated
|
|
349
|
+
headers.each { |name, value| outgoing[name] = value }
|
|
350
|
+
if body
|
|
351
|
+
outgoing.body = json ? JSON.generate(body) : body
|
|
352
|
+
outgoing["Content-Type"] ||= "application/json" if json
|
|
353
|
+
end
|
|
354
|
+
response = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(outgoing) }
|
|
355
|
+
payload = response.body.to_s.empty? ? nil : JSON.parse(response.body)
|
|
356
|
+
unless response.is_a?(Net::HTTPSuccess)
|
|
357
|
+
raise "#{payload&.dig('error', 'message') || payload&.dig('message') || 'StackShift API request failed'} (#{response.code})"
|
|
358
|
+
end
|
|
359
|
+
payload.is_a?(Hash) && payload.key?("data") ? payload["data"] : payload
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def compact(values)
|
|
363
|
+
values.reject { |_key, value| value.nil? || value == "" }
|
|
364
|
+
end
|
|
365
|
+
|
|
366
|
+
def escape(value)
|
|
367
|
+
URI.encode_www_form_component(value.to_s)
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def if_match(revision)
|
|
371
|
+
{ "If-Match" => %("#{Integer(revision)}") }
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module StackShift
|
|
4
|
+
class AssetsRailtie < Rails::Railtie
|
|
5
|
+
initializer "stackshift.assets" do
|
|
6
|
+
Rails.application.config.x.stackshift_assets = lambda do
|
|
7
|
+
StackShift::AssetsClient.new(
|
|
8
|
+
api_key: ENV.fetch("STACKSHIFT_API_KEY"),
|
|
9
|
+
base_url: ENV.fetch("STACKSHIFT_API_URL", "https://api.stackshift.cloud/api/v1"),
|
|
10
|
+
cdn_base_url: ENV.fetch("STACKSHIFT_CDN_URL", "https://cdn.stackshift.cloud")
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: stackshift-assets
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- StackShift
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- support@stackshift.cloud
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- README.md
|
|
21
|
+
- lib/stackshift/assets.rb
|
|
22
|
+
- lib/stackshift/assets_client.rb
|
|
23
|
+
- lib/stackshift/rails.rb
|
|
24
|
+
homepage:
|
|
25
|
+
licenses: []
|
|
26
|
+
metadata: {}
|
|
27
|
+
post_install_message:
|
|
28
|
+
rdoc_options: []
|
|
29
|
+
require_paths:
|
|
30
|
+
- lib
|
|
31
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
32
|
+
requirements:
|
|
33
|
+
- - ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '3.1'
|
|
36
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
requirements: []
|
|
42
|
+
rubygems_version: 3.0.3.1
|
|
43
|
+
signing_key:
|
|
44
|
+
specification_version: 4
|
|
45
|
+
summary: Official StackShift Assets SDK for Ruby and Rails
|
|
46
|
+
test_files: []
|