uppy-s3_multipart 0.1.1 → 0.2.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 +4 -4
- data/README.md +93 -42
- data/lib/shrine/plugins/uppy_s3_multipart.rb +6 -6
- data/lib/uppy/s3_multipart/app.rb +30 -16
- data/lib/uppy/s3_multipart/client.rb +9 -1
- data/uppy-s3_multipart.gemspec +2 -2
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e799b2dfe2ad03929e1e6477382b85c4d721313708abe699673c2c3e496e98e8
|
4
|
+
data.tar.gz: 05d354cff6f1f5a9ae807faffdd91b29d5f16c62fba9abbec82038b7713a1a3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 336d352eeeb31ac132c074ddb4febd73d75f47b5fe41ed69caafb90eecf1d43494f37542d06a1df2859210fe9907207456695c0a3c965131384564f93b0abaa9
|
7
|
+
data.tar.gz: 2ae94a045d3b19e1eabc99dce49ffcaadc1d806caf77cbc87b6edf5b771e1509d58c56a3510f5e1a44b15adfdbe5d93f19fc6acf5322d66bb22791aa5d80d043
|
data/README.md
CHANGED
@@ -10,7 +10,7 @@ uploads.
|
|
10
10
|
Add the gem to your Gemfile:
|
11
11
|
|
12
12
|
```rb
|
13
|
-
gem "uppy-s3_multipart"
|
13
|
+
gem "uppy-s3_multipart", "~> 0.2"
|
14
14
|
```
|
15
15
|
|
16
16
|
## Setup
|
@@ -56,14 +56,14 @@ directly.
|
|
56
56
|
|
57
57
|
### Shrine
|
58
58
|
|
59
|
-
In
|
59
|
+
In your Shrine initializer load the `uppy_s3_multipart` plugin:
|
60
60
|
|
61
61
|
```rb
|
62
62
|
require "shrine"
|
63
63
|
require "shrine/storage/s3"
|
64
64
|
|
65
65
|
Shrine.storages = {
|
66
|
-
cache: Shrine::Storage::S3.new(...),
|
66
|
+
cache: Shrine::Storage::S3.new(prefix: "cache", ...),
|
67
67
|
store: Shrine::Storage::S3.new(...),
|
68
68
|
}
|
69
69
|
|
@@ -78,11 +78,11 @@ inside your main application:
|
|
78
78
|
```rb
|
79
79
|
# Rails (config/routes.rb)
|
80
80
|
Rails.application.routes.draw do
|
81
|
-
mount Shrine.uppy_s3_multipart(:cache) => "/s3"
|
81
|
+
mount Shrine.uppy_s3_multipart(:cache) => "/s3/multipart"
|
82
82
|
end
|
83
83
|
|
84
84
|
# Rack (config.ru)
|
85
|
-
map "/s3" do
|
85
|
+
map "/s3/multipart" do
|
86
86
|
run Shrine.uppy_s3_multipart(:cache)
|
87
87
|
end
|
88
88
|
```
|
@@ -97,14 +97,20 @@ POST /s3/multipart/:uploadId/complete
|
|
97
97
|
DELETE /s3/multipart/:uploadId
|
98
98
|
```
|
99
99
|
|
100
|
-
|
100
|
+
Now in your Uppy configuration point `serverUrl` to your app's URL:
|
101
101
|
|
102
102
|
```js
|
103
103
|
// ...
|
104
104
|
uppy.use(Uppy.AwsS3Multipart, {
|
105
105
|
serverUrl: '/',
|
106
106
|
})
|
107
|
+
```
|
108
|
+
|
109
|
+
In the `upload-success` Uppy callback you can then construct the Shrine
|
110
|
+
uploaded file data (this example assumes your temporary Shrine S3 storage has
|
111
|
+
`prefix: "cache"` set):
|
107
112
|
|
113
|
+
```js
|
108
114
|
uppy.on('upload-success', function (file, data, uploadURL) {
|
109
115
|
var uploadedFileData = JSON.stringify({
|
110
116
|
id: uploadURL.match(/\/cache\/([^\?]+)/)[1], // extract key without prefix
|
@@ -123,8 +129,22 @@ uppy.on('upload-success', function (file, data, uploadURL) {
|
|
123
129
|
Shrine. From there you can swap the `presign_endpoint` + `AwsS3` code with the
|
124
130
|
`uppy_s3_multipart` + `AwsS3Multipart` setup.**
|
125
131
|
|
126
|
-
|
127
|
-
|
132
|
+
Note that by default **Shrine won't extract metadata from directly upload
|
133
|
+
files**, instead it will just copy metadata that was extracted on the client
|
134
|
+
side. See [this section][metadata direct uploads] for the rationale and
|
135
|
+
instructions on how to opt in.
|
136
|
+
|
137
|
+
If you want to make uploads public and have public URLs without query
|
138
|
+
parameters returned, you can pass `public: true` to the Shrine storage (note
|
139
|
+
that this is supported starting from Shrine 2.13).
|
140
|
+
|
141
|
+
```rb
|
142
|
+
Shrine::Storage::S3.new(public: true, **options)
|
143
|
+
```
|
144
|
+
|
145
|
+
Both the plugin and method accept `:options` for specifying additional options
|
146
|
+
to the S3 client operations (see the [Client](#client) section for list of
|
147
|
+
operations and options they accept):
|
128
148
|
|
129
149
|
```rb
|
130
150
|
Shrine.plugin :uppy_s3_multipart, options: {
|
@@ -138,14 +158,11 @@ Shrine.uppy_s3_multipart(:cache, options: {
|
|
138
158
|
})
|
139
159
|
```
|
140
160
|
|
141
|
-
|
142
|
-
files**, instead it will just copy metadata that was extracted on the client
|
143
|
-
side. See [this section][metadata direct uploads] for the rationale and
|
144
|
-
instructions on how to opt in.
|
161
|
+
In the dynamic version the yielded object is an instance of [`Rack::Request`].
|
145
162
|
|
146
|
-
###
|
163
|
+
### App
|
147
164
|
|
148
|
-
You can also use `uppy-s3_multipart` without Shrine
|
165
|
+
You can also use `uppy-s3_multipart` without Shrine, by initializing the
|
149
166
|
`Uppy::S3Multipart::App` directly:
|
150
167
|
|
151
168
|
```rb
|
@@ -162,16 +179,16 @@ bucket = resource.bucket("my-bucket")
|
|
162
179
|
UPPY_S3_MULTIPART_APP = Uppy::S3Multipart::App.new(bucket: bucket)
|
163
180
|
```
|
164
181
|
|
165
|
-
|
182
|
+
You can mount it inside your main app in the same way:
|
166
183
|
|
167
184
|
```rb
|
168
185
|
# Rails (config/routes.rb)
|
169
186
|
Rails.application.routes.draw do
|
170
|
-
mount UPPY_S3_MULTIPART_APP => "/s3"
|
187
|
+
mount UPPY_S3_MULTIPART_APP => "/s3/multipart"
|
171
188
|
end
|
172
189
|
|
173
190
|
# Rack (config.ru)
|
174
|
-
map "/s3" do
|
191
|
+
map "/s3/multipart" do
|
175
192
|
run UPPY_S3_MULTIPART_APP
|
176
193
|
end
|
177
194
|
```
|
@@ -185,25 +202,34 @@ uppy.use(Uppy.AwsS3Multipart, {
|
|
185
202
|
})
|
186
203
|
```
|
187
204
|
|
188
|
-
|
189
|
-
|
190
|
-
|
205
|
+
If you want to make uploads public and have public URLs without query
|
206
|
+
parameters returned, you can pass `public: true` to the app.
|
207
|
+
|
208
|
+
```rb
|
209
|
+
Uppy::S3Multipart::App.new(bucket: bucket, public: true)
|
210
|
+
```
|
211
|
+
|
212
|
+
You can also pass `:options` for specifying additional options to the S3 client
|
213
|
+
operations (see the [Client](#client) section for list of operations and
|
214
|
+
options they accept):
|
191
215
|
|
192
216
|
```rb
|
193
217
|
Uppy::S3Multipart::App.new(bucket: bucket, options: {
|
194
|
-
create_multipart_upload: { acl: "public-read" }
|
218
|
+
create_multipart_upload: { acl: "public-read" } # static
|
195
219
|
})
|
196
220
|
|
197
221
|
# OR
|
198
222
|
|
199
223
|
Uppy::S3Multipart::App.new(bucket: bucket, options: {
|
200
|
-
create_multipart_upload: -> (request) { { acl: "public-read" } }
|
224
|
+
create_multipart_upload: -> (request) { { acl: "public-read" } } # dynamic
|
201
225
|
})
|
202
226
|
```
|
203
227
|
|
204
|
-
|
228
|
+
In the dynamic version the yielded object is an instance of [`Rack::Request`].
|
229
|
+
|
230
|
+
### Client
|
205
231
|
|
206
|
-
If you would rather implement the endpoints yourself, you can utilize
|
232
|
+
If you would rather implement the endpoints yourself, you can utilize the
|
207
233
|
`Uppy::S3Multipart::Client` to make S3 requests.
|
208
234
|
|
209
235
|
```rb
|
@@ -223,13 +249,13 @@ client.create_multipart_upload(key: "foo", **options)
|
|
223
249
|
|
224
250
|
Accepts:
|
225
251
|
|
226
|
-
* `:key`
|
252
|
+
* `:key` – object key
|
227
253
|
* additional options for [`Aws::S3::Client#create_multipart_upload`]
|
228
254
|
|
229
255
|
Returns:
|
230
256
|
|
231
|
-
* `:upload_id`
|
232
|
-
* `:key`
|
257
|
+
* `:upload_id` – id of the created multipart upload
|
258
|
+
* `:key` – object key
|
233
259
|
|
234
260
|
#### `#list_parts`
|
235
261
|
|
@@ -244,17 +270,17 @@ client.list_parts(upload_id: "MultipartUploadId", key: "foo", **options)
|
|
244
270
|
|
245
271
|
Accepts:
|
246
272
|
|
247
|
-
* `:upload_id`
|
248
|
-
* `:key`
|
273
|
+
* `:upload_id` – multipart upload id
|
274
|
+
* `:key` – object key
|
249
275
|
* additional options for [`Aws::S3::Client#list_parts`]
|
250
276
|
|
251
277
|
Returns:
|
252
278
|
|
253
279
|
* array of parts
|
254
280
|
|
255
|
-
- `:part_number`
|
256
|
-
- `:size`
|
257
|
-
- `:etag`
|
281
|
+
- `:part_number` – position of the part
|
282
|
+
- `:size` – filesize of the part
|
283
|
+
- `:etag` – etag of the part
|
258
284
|
|
259
285
|
#### `#prepare_upload_part`
|
260
286
|
|
@@ -267,14 +293,14 @@ client.prepare_upload_part(upload_id: "MultipartUploadId", key: "foo", part_numb
|
|
267
293
|
|
268
294
|
Accepts:
|
269
295
|
|
270
|
-
* `:upload_id`
|
271
|
-
* `:key`
|
272
|
-
* `:part_number`
|
296
|
+
* `:upload_id` – multipart upload id
|
297
|
+
* `:key` – object key
|
298
|
+
* `:part_number` – number of the next part
|
273
299
|
* additional options for [`Aws::S3::Client#upload_part`] and [`Aws::S3::Presigner#presigned_url`]
|
274
300
|
|
275
301
|
Returns:
|
276
302
|
|
277
|
-
* `:url`
|
303
|
+
* `:url` – endpoint that should be used for uploading a new multipart part via a `PUT` request
|
278
304
|
|
279
305
|
#### `#complete_multipart_upload`
|
280
306
|
|
@@ -287,14 +313,36 @@ client.complete_multipart_upload(upload_id: upload_id, key: key, parts: [{ part_
|
|
287
313
|
|
288
314
|
Accepts:
|
289
315
|
|
290
|
-
* `:upload_id`
|
291
|
-
* `:key`
|
292
|
-
* `:parts`
|
316
|
+
* `:upload_id` – multipart upload id
|
317
|
+
* `:key` – object key
|
318
|
+
* `:parts` – list of all uploaded parts, consisting of `:part_number` and `:etag`
|
293
319
|
* additional options for [`Aws::S3::Client#complete_multipart_upload`]
|
294
320
|
|
295
321
|
Returns:
|
296
322
|
|
297
|
-
* `:location`
|
323
|
+
* `:location` – URL to the uploaded object
|
324
|
+
|
325
|
+
#### `#object_url`
|
326
|
+
|
327
|
+
Generates URL to the object.
|
328
|
+
|
329
|
+
```rb
|
330
|
+
client.object_url(key: key, **options)
|
331
|
+
# => "https://my-bucket.s3.amazonaws.com/foo?..."
|
332
|
+
```
|
333
|
+
|
334
|
+
This is called after `#complete_multipart_upload` in the app and returned in
|
335
|
+
the response.
|
336
|
+
|
337
|
+
Accepts:
|
338
|
+
|
339
|
+
* `:key` – object key
|
340
|
+
* `:public` – for generating a public URL (default is presigned expiring URL)
|
341
|
+
* additional options for [`Aws::S3::Object#presigned_url`] and [`Aws::S3::Client#get_object`]
|
342
|
+
|
343
|
+
Returns:
|
344
|
+
|
345
|
+
* URL to the object
|
298
346
|
|
299
347
|
#### `#abort_multipart_upload`
|
300
348
|
|
@@ -307,8 +355,8 @@ client.abort_multipart_upload(upload_id: upload_id, key: key, **options)
|
|
307
355
|
|
308
356
|
Accepts:
|
309
357
|
|
310
|
-
* `:upload_id`
|
311
|
-
* `:key`
|
358
|
+
* `:upload_id` – multipart upload id
|
359
|
+
* `:key` – object key
|
312
360
|
* additional options for [`Aws::S3::Client#abort_multipart_upload`]
|
313
361
|
|
314
362
|
## Contributing
|
@@ -331,10 +379,13 @@ License](https://opensource.org/licenses/MIT).
|
|
331
379
|
[AwsS3Multipart]: https://uppy.io/docs/aws-s3-multipart/
|
332
380
|
[Shrine]: https://shrinerb.com
|
333
381
|
[Adding Direct S3 Uploads]: https://github.com/shrinerb/shrine/wiki/Adding-Direct-S3-Uploads
|
382
|
+
[`Rack::Request`]: https://www.rubydoc.info/github/rack/rack/master/Rack/Request
|
334
383
|
[`Aws::S3::Client#create_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#create_multipart_upload-instance_method
|
335
384
|
[`Aws::S3::Client#list_parts`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#list_parts-instance_method
|
336
385
|
[`Aws::S3::Client#upload_part`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#upload_part-instance_method
|
337
386
|
[`Aws::S3::Presigner#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Presigner.html#presigned_url-instance_method
|
338
387
|
[`Aws::S3::Client#complete_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#complete_multipart_upload-instance_method
|
388
|
+
[`Aws::S3::Object#presigned_url`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Object.html#presigned_url-instance_method
|
389
|
+
[`Aws::S3::Client#get_object`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#get_object-instance_method
|
339
390
|
[`Aws::S3::Client#abort_multipart_upload`]: https://docs.aws.amazon.com/sdk-for-ruby/v3/api/Aws/S3/Client.html#abort_multipart_upload-instance_method
|
340
391
|
[metadata direct uploads]: https://github.com/shrinerb/shrine/blob/master/doc/metadata.md#direct-uploads
|
@@ -15,12 +15,12 @@ class Shrine
|
|
15
15
|
fail Error, "expected storage to be a Shrine::Storage::S3, but was #{s3.inspect}"
|
16
16
|
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
)
|
18
|
+
options[:bucket] ||= s3.bucket
|
19
|
+
options[:prefix] ||= s3.prefix
|
20
|
+
options[:public] ||= s3.public if s3.respond_to?(:public)
|
21
|
+
options[:options] ||= opts[:uppy_s3_multipart_options]
|
22
|
+
|
23
|
+
::Uppy::S3Multipart::App.new(**options)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -8,10 +8,11 @@ require "cgi"
|
|
8
8
|
module Uppy
|
9
9
|
module S3Multipart
|
10
10
|
class App
|
11
|
-
def initialize(bucket:, prefix: nil, options: {})
|
11
|
+
def initialize(bucket:, prefix: nil, public: nil, options: {})
|
12
12
|
@router = Class.new(Router)
|
13
13
|
@router.opts[:client] = Client.new(bucket: bucket)
|
14
14
|
@router.opts[:prefix] = prefix
|
15
|
+
@router.opts[:public] = public
|
15
16
|
@router.opts[:options] = options
|
16
17
|
end
|
17
18
|
|
@@ -24,10 +25,14 @@ module Uppy
|
|
24
25
|
plugin :json
|
25
26
|
plugin :json_parser
|
26
27
|
plugin :halt
|
28
|
+
plugin :path_rewriter
|
29
|
+
|
30
|
+
# allow mounting on "/s3" for backwards compatibility
|
31
|
+
rewrite_path "/multipart", ""
|
27
32
|
|
28
33
|
route do |r|
|
29
|
-
# POST /multipart
|
30
|
-
r.post
|
34
|
+
# POST /s3/multipart
|
35
|
+
r.post true do
|
31
36
|
content_type = r.params["type"]
|
32
37
|
filename = r.params["filename"]
|
33
38
|
|
@@ -38,13 +43,16 @@ module Uppy
|
|
38
43
|
# CGI-escape the filename because aws-sdk's signature calculator trips on special characters
|
39
44
|
content_disposition = "inline; filename=\"#{CGI.escape(filename)}\"" if filename
|
40
45
|
|
41
|
-
|
46
|
+
options = { content_type: content_type, content_disposition: content_disposition }
|
47
|
+
options[:acl] = "public-read" if opts[:public]
|
48
|
+
|
49
|
+
result = client_call(:create_multipart_upload, key: key, **options)
|
42
50
|
|
43
51
|
{ uploadId: result.fetch(:upload_id), key: result.fetch(:key) }
|
44
52
|
end
|
45
53
|
|
46
|
-
# GET /multipart/:uploadId
|
47
|
-
r.get
|
54
|
+
# GET /s3/multipart/:uploadId
|
55
|
+
r.get String do |upload_id|
|
48
56
|
key = param!("key")
|
49
57
|
|
50
58
|
result = client_call(:list_parts, upload_id: upload_id, key: key)
|
@@ -54,8 +62,8 @@ module Uppy
|
|
54
62
|
end
|
55
63
|
end
|
56
64
|
|
57
|
-
# GET /multipart/:uploadId/:partNumber
|
58
|
-
r.get
|
65
|
+
# GET /s3/multipart/:uploadId/:partNumber
|
66
|
+
r.get String, String do |upload_id, part_number|
|
59
67
|
key = param!("key")
|
60
68
|
|
61
69
|
result = client_call(:prepare_upload_part, upload_id: upload_id, key: key, part_number: part_number)
|
@@ -63,8 +71,8 @@ module Uppy
|
|
63
71
|
{ url: result.fetch(:url) }
|
64
72
|
end
|
65
73
|
|
66
|
-
# POST /multipart/:uploadId/complete
|
67
|
-
r.post
|
74
|
+
# POST /s3/multipart/:uploadId/complete
|
75
|
+
r.post String, "complete" do |upload_id|
|
68
76
|
key = param!("key")
|
69
77
|
parts = param!("parts")
|
70
78
|
|
@@ -72,17 +80,19 @@ module Uppy
|
|
72
80
|
begin
|
73
81
|
{ part_number: part.fetch("PartNumber"), etag: part.fetch("ETag") }
|
74
82
|
rescue KeyError
|
75
|
-
|
83
|
+
error! "At least one part is missing \"PartNumber\" or \"ETag\" field"
|
76
84
|
end
|
77
85
|
end
|
78
86
|
|
79
|
-
|
87
|
+
client_call(:complete_multipart_upload, upload_id: upload_id, key: key, parts: parts)
|
88
|
+
|
89
|
+
object_url = client_call(:object_url, key: key, public: opts[:public])
|
80
90
|
|
81
|
-
{ location:
|
91
|
+
{ location: object_url }
|
82
92
|
end
|
83
93
|
|
84
|
-
# DELETE /multipart/:uploadId
|
85
|
-
r.delete
|
94
|
+
# DELETE /s3/multipart/:uploadId
|
95
|
+
r.delete String do |upload_id|
|
86
96
|
key = param!("key")
|
87
97
|
|
88
98
|
client_call(:abort_multipart_upload, upload_id: upload_id, key: key)
|
@@ -105,10 +115,14 @@ module Uppy
|
|
105
115
|
def param!(name)
|
106
116
|
value = request.params[name]
|
107
117
|
|
108
|
-
|
118
|
+
error! "Missing \"#{name}\" parameter" if value.nil?
|
109
119
|
|
110
120
|
value
|
111
121
|
end
|
122
|
+
|
123
|
+
def error!(message)
|
124
|
+
request.halt 400, { error: message }
|
125
|
+
end
|
112
126
|
end
|
113
127
|
end
|
114
128
|
end
|
@@ -43,7 +43,15 @@ module Uppy
|
|
43
43
|
**options
|
44
44
|
)
|
45
45
|
|
46
|
-
{ location:
|
46
|
+
{ location: object_url(key: key) }
|
47
|
+
end
|
48
|
+
|
49
|
+
def object_url(key:, public: nil, **options)
|
50
|
+
if public
|
51
|
+
object(key).public_url(**options)
|
52
|
+
else
|
53
|
+
object(key).presigned_url(:get, **options)
|
54
|
+
end
|
47
55
|
end
|
48
56
|
|
49
57
|
def abort_multipart_upload(upload_id:, key:, **options)
|
data/uppy-s3_multipart.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |gem|
|
2
2
|
gem.name = "uppy-s3_multipart"
|
3
|
-
gem.version = "0.
|
3
|
+
gem.version = "0.2.0"
|
4
4
|
|
5
5
|
gem.required_ruby_version = ">= 2.2"
|
6
6
|
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |gem|
|
|
19
19
|
gem.add_development_dependency "rake"
|
20
20
|
gem.add_development_dependency "minitest"
|
21
21
|
gem.add_development_dependency "rack-test_app"
|
22
|
-
gem.add_development_dependency "shrine", "~> 2.
|
22
|
+
gem.add_development_dependency "shrine", "~> 2.13"
|
23
23
|
gem.add_development_dependency "shrine-memory"
|
24
24
|
gem.add_development_dependency "aws-sdk-core", "~> 3.23"
|
25
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uppy-s3_multipart
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Janko Marohnić
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: roda
|
@@ -92,14 +92,14 @@ dependencies:
|
|
92
92
|
requirements:
|
93
93
|
- - "~>"
|
94
94
|
- !ruby/object:Gem::Version
|
95
|
-
version: '2.
|
95
|
+
version: '2.13'
|
96
96
|
type: :development
|
97
97
|
prerelease: false
|
98
98
|
version_requirements: !ruby/object:Gem::Requirement
|
99
99
|
requirements:
|
100
100
|
- - "~>"
|
101
101
|
- !ruby/object:Gem::Version
|
102
|
-
version: '2.
|
102
|
+
version: '2.13'
|
103
103
|
- !ruby/object:Gem::Dependency
|
104
104
|
name: shrine-memory
|
105
105
|
requirement: !ruby/object:Gem::Requirement
|