middleman-s3_sync 4.0.3 → 4.5.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 +5 -5
- data/.gitignore +1 -0
- data/README.md +8 -6
- data/lib/middleman/redirect.rb +21 -0
- data/lib/middleman/s3_sync/options.rb +6 -0
- data/lib/middleman/s3_sync/resource.rb +129 -24
- data/lib/middleman/s3_sync/version.rb +1 -1
- data/lib/middleman/s3_sync.rb +59 -41
- data/lib/middleman-s3_sync/commands.rb +3 -1
- data/lib/middleman-s3_sync/extension.rb +14 -3
- data/lib/middleman-s3_sync.rb +1 -0
- data/middleman-s3_sync.gemspec +8 -5
- data/spec/resource_spec.rb +78 -13
- data/spec/spec_helper.rb +41 -1
- metadata +58 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 0e217a8648de15ba3454749ebf5373e9e2bf31591078be5ae0f5128aeac21d79
|
4
|
+
data.tar.gz: 4f82b54882d96b1ece6585e2075a19d8ef54af78fb8aaec42c07ca14cf41fc7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2640264fb1f0d4c2be50526d966842176ab9997537a4d2f271b0e1e0e29e4398da44f2c66969b08d9f9f4ddeaeef2014a2b2afe3eb68c95986725aa399524f3c
|
7
|
+
data.tar.gz: b9fa2b48b0f63638627750a532e7c7f1fd94a15cf1af4bd9a9826a24021b9fce288d83c9e5d240b4483a0e0488f57b0a7726ede24af9a861d922095bd26eabda
|
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Middleman::S3Sync
|
2
2
|
|
3
|
-
[](https://gitter.im/fredjean/middleman-s3_sync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://gitter.im/fredjean/middleman-s3_sync?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [](https://codeclimate.com/github/fredjean/middleman-s3_sync) [](https://travis-ci.org/fredjean/middleman-s3_sync)
|
4
4
|
|
5
5
|
This gem determines which files need to be added, updated and optionally deleted
|
6
6
|
and only transfer these files up. This reduces the impact of an update
|
@@ -79,7 +79,8 @@ The following defaults apply to the configuration items:
|
|
79
79
|
|
80
80
|
## Setting AWS Credentials
|
81
81
|
|
82
|
-
There are several ways to provide the AWS credentials for s3_sync
|
82
|
+
There are several ways to provide the AWS credentials for s3_sync. I strongly recommend using some form of federation to assume a role with permissions to publish to your
|
83
|
+
S3 bucket. However, you can still use the following methods::We
|
83
84
|
|
84
85
|
#### Through `config.rb`
|
85
86
|
|
@@ -118,6 +119,7 @@ map to the following values:
|
|
118
119
|
| --------------------- | ---------------------------------- |
|
119
120
|
| aws_access_key_id | ```ENV['AWS_ACCESS_KEY_ID']``` |
|
120
121
|
| aws_secret_access_key | ```ENV['AWS_SECRET_ACCESS_KEY']``` |
|
122
|
+
| aws_session_token | ```ENV['AWS_SESSION_TOKEN']``` |
|
121
123
|
| bucket | ```ENV['AWS_BUCKET']``` |
|
122
124
|
|
123
125
|
The environment is used when the credentials are not set in the activate
|
@@ -182,17 +184,17 @@ You can specify which environment to run Middleman under using the
|
|
182
184
|
|
183
185
|
$ middleman s3_sync --environment=production
|
184
186
|
|
185
|
-
You can set up separate sync environments in config.rb like this:
|
187
|
+
You can set up separate sync environments in config.rb like this:
|
186
188
|
|
187
189
|
```ruby
|
188
190
|
configure :staging do
|
189
191
|
activate :s3_sync do |s3_sync|
|
190
192
|
s3_sync.bucket = '<bucket'
|
191
|
-
...
|
193
|
+
...
|
192
194
|
end
|
193
195
|
end
|
194
196
|
```
|
195
|
-
|
197
|
+
|
196
198
|
See the Usage section above for all the s3_sync. options to include. Currently, the .s3_sync file does not allow separate environments.
|
197
199
|
|
198
200
|
#### Dry Run
|
@@ -291,7 +293,7 @@ The following keys can be set:
|
|
291
293
|
You can pass the `expires` key to the `caching_policy` and
|
292
294
|
`default_caching_policy` methods if you insist on setting the expires
|
293
295
|
header on a results. You will need to pass it a Time object indicating
|
294
|
-
when the
|
296
|
+
when the resource is set to expire.
|
295
297
|
|
296
298
|
> Note that the `Cache-Control` header will take precedence over the
|
297
299
|
> `Expires` header if both are present.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Middleman
|
2
|
+
module Sitemap
|
3
|
+
class Resource
|
4
|
+
def redirect?
|
5
|
+
false
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
module Extensions
|
10
|
+
class RedirectResource < Resource
|
11
|
+
def target_url
|
12
|
+
@target_url ||= ::Middleman::Util.url_for(@store.app, @request_path, relative: false, find_resource: true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def redirect?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -6,6 +6,7 @@ module Middleman
|
|
6
6
|
:http_prefix,
|
7
7
|
:acl,
|
8
8
|
:bucket,
|
9
|
+
:endpoint,
|
9
10
|
:region,
|
10
11
|
:aws_access_key_id,
|
11
12
|
:aws_secret_access_key,
|
@@ -22,6 +23,7 @@ module Middleman
|
|
22
23
|
:dry_run,
|
23
24
|
:verbose,
|
24
25
|
:content_types,
|
26
|
+
:ignore_paths,
|
25
27
|
:index_document,
|
26
28
|
:error_document
|
27
29
|
]
|
@@ -67,6 +69,10 @@ module Middleman
|
|
67
69
|
(@path_style.nil? ? true : @path_style)
|
68
70
|
end
|
69
71
|
|
72
|
+
def ignore_paths
|
73
|
+
@ignore_paths.nil? ? [] : @ignore_paths
|
74
|
+
end
|
75
|
+
|
70
76
|
def prefix=(prefix)
|
71
77
|
http_prefix = @http_prefix ? @http_prefix.sub(%r{^/}, "") : ""
|
72
78
|
if http_prefix.split("/").first == prefix
|
@@ -4,12 +4,19 @@ module Middleman
|
|
4
4
|
attr_accessor :path, :resource, :partial_s3_resource, :full_s3_resource, :content_type, :gzipped, :options
|
5
5
|
|
6
6
|
CONTENT_MD5_KEY = 'x-amz-meta-content-md5'
|
7
|
+
REDIRECT_KEY = 'x-amz-website-redirect-location'
|
7
8
|
|
8
9
|
include Status
|
9
10
|
|
10
11
|
def initialize(resource, partial_s3_resource)
|
11
12
|
@resource = resource
|
12
|
-
@path =
|
13
|
+
@path = if resource
|
14
|
+
resource.destination_path.sub(/^\//, '')
|
15
|
+
elsif partial_s3_resource&.key
|
16
|
+
partial_s3_resource.key.sub(/^\//, '')
|
17
|
+
else
|
18
|
+
''
|
19
|
+
end
|
13
20
|
@partial_s3_resource = partial_s3_resource
|
14
21
|
end
|
15
22
|
|
@@ -19,13 +26,33 @@ module Middleman
|
|
19
26
|
|
20
27
|
# S3 resource as returned by a HEAD request
|
21
28
|
def full_s3_resource
|
22
|
-
@full_s3_resource ||=
|
29
|
+
@full_s3_resource ||= begin
|
30
|
+
bucket.object(remote_path.sub(/^\//, '')).head
|
31
|
+
rescue Aws::S3::Errors::NotFound
|
32
|
+
nil
|
33
|
+
end
|
23
34
|
end
|
24
35
|
|
25
36
|
def remote_path
|
26
|
-
|
37
|
+
if s3_resource
|
38
|
+
if s3_resource.respond_to?(:key)
|
39
|
+
s3_resource.key.sub(/^\//, '')
|
40
|
+
else
|
41
|
+
# For HeadObjectOutput objects which don't have key method
|
42
|
+
options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
|
43
|
+
end
|
44
|
+
else
|
45
|
+
options.prefix ? normalize_path(options.prefix, path) : path.sub(/^\//, '')
|
46
|
+
end.sub(/^\//, '') # Ensure no leading slash
|
27
47
|
end
|
28
48
|
alias :key :remote_path
|
49
|
+
|
50
|
+
def normalize_path(prefix, path)
|
51
|
+
# Remove any trailing slash from prefix and leading slash from path
|
52
|
+
prefix = prefix.chomp('/')
|
53
|
+
path = path.sub(/^\//, '')
|
54
|
+
"#{prefix}/#{path}"
|
55
|
+
end
|
29
56
|
|
30
57
|
def to_h
|
31
58
|
attributes = {
|
@@ -52,18 +79,19 @@ module Middleman
|
|
52
79
|
attributes[:encryption] = 'AES256'
|
53
80
|
end
|
54
81
|
|
82
|
+
if redirect?
|
83
|
+
attributes[REDIRECT_KEY] = redirect_url
|
84
|
+
end
|
85
|
+
|
55
86
|
attributes
|
56
87
|
end
|
57
88
|
alias :attributes :to_h
|
58
89
|
|
59
90
|
def update!
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
s3_resource.save unless options.dry_run
|
66
|
-
}
|
91
|
+
say_status "#{ANSI.blue{"Updating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
|
92
|
+
unless options.dry_run
|
93
|
+
upload!
|
94
|
+
end
|
67
95
|
end
|
68
96
|
|
69
97
|
def local_path
|
@@ -77,14 +105,52 @@ module Middleman
|
|
77
105
|
|
78
106
|
def destroy!
|
79
107
|
say_status "#{ANSI.red{"Deleting"}} #{remote_path}"
|
80
|
-
bucket.
|
108
|
+
bucket.object(remote_path.sub(/^\//, '')).delete unless options.dry_run
|
81
109
|
end
|
82
110
|
|
83
111
|
def create!
|
84
112
|
say_status "#{ANSI.green{"Creating"}} #{remote_path}#{ gzipped ? ANSI.white {' (gzipped)'} : ''}"
|
85
|
-
|
86
|
-
|
113
|
+
unless options.dry_run
|
114
|
+
upload!
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def upload!
|
119
|
+
object = bucket.object(remote_path.sub(/^\//, ''))
|
120
|
+
upload_options = {
|
121
|
+
body: local_content,
|
122
|
+
content_type: content_type,
|
123
|
+
acl: options.acl
|
87
124
|
}
|
125
|
+
|
126
|
+
# Add metadata if present
|
127
|
+
if local_content_md5
|
128
|
+
upload_options[:metadata] = { CONTENT_MD5_KEY => local_content_md5 }
|
129
|
+
end
|
130
|
+
|
131
|
+
# Add redirect if present
|
132
|
+
upload_options[:website_redirect_location] = redirect_url if redirect?
|
133
|
+
|
134
|
+
# Add content encoding if present
|
135
|
+
upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped
|
136
|
+
|
137
|
+
# Add cache control and expires if present
|
138
|
+
if caching_policy
|
139
|
+
upload_options[:cache_control] = caching_policy.cache_control
|
140
|
+
upload_options[:expires] = caching_policy.expires
|
141
|
+
end
|
142
|
+
|
143
|
+
# Add storage class if needed
|
144
|
+
if options.reduced_redundancy_storage
|
145
|
+
upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
|
146
|
+
end
|
147
|
+
|
148
|
+
# Add encryption if needed
|
149
|
+
if options.encryption
|
150
|
+
upload_options[:server_side_encryption] = 'AES256'
|
151
|
+
end
|
152
|
+
|
153
|
+
object.put(upload_options)
|
88
154
|
end
|
89
155
|
|
90
156
|
def ignore!
|
@@ -122,12 +188,18 @@ module Middleman
|
|
122
188
|
status == :ignored || status == :alternate_encoding
|
123
189
|
end
|
124
190
|
|
125
|
-
def local_content
|
126
|
-
|
191
|
+
def local_content
|
192
|
+
if block_given?
|
193
|
+
File.open(local_path) { |f| yield f.read }
|
194
|
+
else
|
195
|
+
File.read(local_path)
|
196
|
+
end
|
127
197
|
end
|
128
198
|
|
129
199
|
def status
|
130
|
-
@status ||= if
|
200
|
+
@status ||= if shunned?
|
201
|
+
:ignored
|
202
|
+
elsif directory?
|
131
203
|
if remote?
|
132
204
|
:deleted
|
133
205
|
else
|
@@ -136,7 +208,7 @@ module Middleman
|
|
136
208
|
elsif local? && remote?
|
137
209
|
if options.force
|
138
210
|
:updated
|
139
|
-
elsif not
|
211
|
+
elsif not metadata_match?
|
140
212
|
:updated
|
141
213
|
elsif local_object_md5 == remote_object_md5
|
142
214
|
:identical
|
@@ -170,11 +242,36 @@ module Middleman
|
|
170
242
|
end
|
171
243
|
|
172
244
|
def remote?
|
173
|
-
|
245
|
+
!full_s3_resource.nil?
|
174
246
|
end
|
175
247
|
|
176
248
|
def redirect?
|
177
|
-
|
249
|
+
(resource && resource.respond_to?(:redirect?) && resource.redirect?) ||
|
250
|
+
(full_s3_resource && full_s3_resource.respond_to?(:website_redirect_location) && full_s3_resource.website_redirect_location)
|
251
|
+
end
|
252
|
+
|
253
|
+
def metadata_match?
|
254
|
+
redirect_match? && caching_policy_match?
|
255
|
+
end
|
256
|
+
|
257
|
+
def redirect_match?
|
258
|
+
if redirect?
|
259
|
+
redirect_url == remote_redirect_url
|
260
|
+
else
|
261
|
+
true
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
def shunned?
|
266
|
+
!!path[Regexp.union(options.ignore_paths)]
|
267
|
+
end
|
268
|
+
|
269
|
+
def remote_redirect_url
|
270
|
+
full_s3_resource&.website_redirect_location
|
271
|
+
end
|
272
|
+
|
273
|
+
def redirect_url
|
274
|
+
resource.respond_to?(:target_url) ? resource.target_url : nil
|
178
275
|
end
|
179
276
|
|
180
277
|
def directory?
|
@@ -186,7 +283,7 @@ module Middleman
|
|
186
283
|
end
|
187
284
|
|
188
285
|
def remote_object_md5
|
189
|
-
s3_resource.etag
|
286
|
+
s3_resource.etag.gsub(/"/, '') if s3_resource.etag
|
190
287
|
end
|
191
288
|
|
192
289
|
def encoding_match?
|
@@ -194,7 +291,9 @@ module Middleman
|
|
194
291
|
end
|
195
292
|
|
196
293
|
def remote_content_md5
|
197
|
-
full_s3_resource.metadata
|
294
|
+
if full_s3_resource && full_s3_resource.metadata
|
295
|
+
full_s3_resource.metadata[CONTENT_MD5_KEY]
|
296
|
+
end
|
198
297
|
end
|
199
298
|
|
200
299
|
def local_object_md5
|
@@ -202,7 +301,13 @@ module Middleman
|
|
202
301
|
end
|
203
302
|
|
204
303
|
def local_content_md5
|
205
|
-
@local_content_md5 ||=
|
304
|
+
@local_content_md5 ||= begin
|
305
|
+
if File.exist?(original_path)
|
306
|
+
Digest::MD5.hexdigest(File.read(original_path))
|
307
|
+
else
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
end
|
206
311
|
end
|
207
312
|
|
208
313
|
def original_path
|
@@ -211,7 +316,7 @@ module Middleman
|
|
211
316
|
|
212
317
|
def content_type
|
213
318
|
@content_type ||= Middleman::S3Sync.content_types[local_path]
|
214
|
-
@content_type ||= !resource.nil? ? resource.content_type : nil
|
319
|
+
@content_type ||= !resource.nil? && resource.respond_to?(:content_type) ? resource.content_type : nil
|
215
320
|
end
|
216
321
|
|
217
322
|
def caching_policy
|
@@ -219,7 +324,7 @@ module Middleman
|
|
219
324
|
end
|
220
325
|
|
221
326
|
def caching_policy_match?
|
222
|
-
if (
|
327
|
+
if caching_policy && full_s3_resource && full_s3_resource.respond_to?(:cache_control)
|
223
328
|
caching_policy.cache_control == full_s3_resource.cache_control
|
224
329
|
else
|
225
330
|
true
|
data/lib/middleman/s3_sync.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
|
-
require '
|
2
|
-
require 'fog/aws/storage'
|
1
|
+
require 'aws-sdk-s3'
|
3
2
|
require 'digest/md5'
|
4
3
|
require 'middleman/s3_sync/version'
|
5
4
|
require 'middleman/s3_sync/options'
|
@@ -7,6 +6,7 @@ require 'middleman/s3_sync/caching_policy'
|
|
7
6
|
require 'middleman/s3_sync/status'
|
8
7
|
require 'middleman/s3_sync/resource'
|
9
8
|
require 'middleman-s3_sync/extension'
|
9
|
+
require 'middleman/redirect'
|
10
10
|
require 'parallel'
|
11
11
|
require 'ruby-progressbar'
|
12
12
|
require 'thread'
|
@@ -24,6 +24,8 @@ module Middleman
|
|
24
24
|
attr_accessor :mm_resources
|
25
25
|
attr_reader :app
|
26
26
|
|
27
|
+
THREADS_COUNT = 8
|
28
|
+
|
27
29
|
def sync()
|
28
30
|
@app ||= ::Middleman::Application.new
|
29
31
|
|
@@ -48,8 +50,8 @@ module Middleman
|
|
48
50
|
def bucket
|
49
51
|
@@bucket_lock.synchronize do
|
50
52
|
@bucket ||= begin
|
51
|
-
bucket =
|
52
|
-
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket
|
53
|
+
bucket = s3_resource.bucket(s3_sync_options.bucket)
|
54
|
+
raise "Bucket #{s3_sync_options.bucket} doesn't exist!" unless bucket.exists?
|
53
55
|
bucket
|
54
56
|
end
|
55
57
|
end
|
@@ -73,7 +75,12 @@ module Middleman
|
|
73
75
|
|
74
76
|
protected
|
75
77
|
def update_bucket_versioning
|
76
|
-
|
78
|
+
s3_client.put_bucket_versioning({
|
79
|
+
bucket: s3_sync_options.bucket,
|
80
|
+
versioning_configuration: {
|
81
|
+
status: "Enabled"
|
82
|
+
}
|
83
|
+
}) if s3_sync_options.version_bucket
|
77
84
|
end
|
78
85
|
|
79
86
|
def update_bucket_website
|
@@ -87,30 +94,47 @@ module Middleman
|
|
87
94
|
|
88
95
|
unless opts.empty?
|
89
96
|
say_status "Putting bucket website: #{opts.to_json}"
|
90
|
-
|
97
|
+
s3_client.put_bucket_website({
|
98
|
+
bucket: s3_sync_options.bucket,
|
99
|
+
website_configuration: opts
|
100
|
+
})
|
91
101
|
end
|
92
102
|
end
|
93
103
|
|
94
|
-
def
|
95
|
-
|
96
|
-
|
97
|
-
:path_style => s3_sync_options.path_style
|
98
|
-
}
|
104
|
+
def s3_client
|
105
|
+
@s3_client ||= Aws::S3::Client.new(connection_options)
|
106
|
+
end
|
99
107
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
connection_options
|
107
|
-
|
108
|
+
def s3_resource
|
109
|
+
@s3_resource ||= Aws::S3::Resource.new(client: s3_client)
|
110
|
+
end
|
111
|
+
|
112
|
+
def connection_options
|
113
|
+
@connection_options ||= begin
|
114
|
+
connection_options = {
|
115
|
+
endpoint: s3_sync_options.endpoint,
|
116
|
+
region: s3_sync_options.region,
|
117
|
+
force_path_style: s3_sync_options.path_style
|
118
|
+
}
|
119
|
+
|
120
|
+
if s3_sync_options.aws_access_key_id && s3_sync_options.aws_secret_access_key
|
121
|
+
connection_options.merge!({
|
122
|
+
access_key_id: s3_sync_options.aws_access_key_id,
|
123
|
+
secret_access_key: s3_sync_options.aws_secret_access_key
|
124
|
+
})
|
125
|
+
|
126
|
+
# If using an assumed role
|
127
|
+
connection_options.merge!({
|
128
|
+
session_token: s3_sync_options.aws_session_token
|
129
|
+
}) if s3_sync_options.aws_session_token
|
130
|
+
end
|
108
131
|
|
109
|
-
|
132
|
+
connection_options
|
133
|
+
end
|
110
134
|
end
|
111
135
|
|
112
136
|
def remote_resource_for_path(path)
|
113
|
-
bucket_files
|
137
|
+
bucket_files[path]
|
114
138
|
end
|
115
139
|
|
116
140
|
def s3_sync_resources
|
@@ -125,7 +149,7 @@ module Middleman
|
|
125
149
|
|
126
150
|
def remote_paths
|
127
151
|
@remote_paths ||= if s3_sync_options.delete
|
128
|
-
bucket_files.
|
152
|
+
bucket_files.keys
|
129
153
|
else
|
130
154
|
[]
|
131
155
|
end
|
@@ -133,42 +157,36 @@ module Middleman
|
|
133
157
|
|
134
158
|
def bucket_files
|
135
159
|
@@bucket_files_lock.synchronize do
|
136
|
-
@bucket_files ||=
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
160
|
+
@bucket_files ||= begin
|
161
|
+
files = {}
|
162
|
+
bucket.objects.each do |object|
|
163
|
+
files[object.key] = object
|
164
|
+
end
|
165
|
+
files
|
166
|
+
end
|
141
167
|
end
|
142
168
|
end
|
143
169
|
|
144
170
|
def create_resources
|
145
|
-
files_to_create
|
146
|
-
r.create!
|
147
|
-
end
|
171
|
+
Parallel.map(files_to_create, in_threads: THREADS_COUNT, &:create!)
|
148
172
|
end
|
149
173
|
|
150
174
|
def update_resources
|
151
|
-
files_to_update
|
152
|
-
r.update!
|
153
|
-
end
|
175
|
+
Parallel.map(files_to_update, in_threads: THREADS_COUNT, &:update!)
|
154
176
|
end
|
155
177
|
|
156
178
|
def delete_resources
|
157
|
-
files_to_delete
|
158
|
-
r.destroy!
|
159
|
-
end
|
179
|
+
Parallel.map(files_to_delete, in_threads: THREADS_COUNT, &:destroy!)
|
160
180
|
end
|
161
181
|
|
162
182
|
def ignore_resources
|
163
|
-
files_to_ignore
|
164
|
-
r.ignore!
|
165
|
-
end
|
183
|
+
Parallel.map(files_to_ignore, in_threads: THREADS_COUNT, &:ignore!)
|
166
184
|
end
|
167
185
|
|
168
186
|
def work_to_be_done?
|
169
|
-
Parallel.each(mm_resources, in_threads:
|
187
|
+
Parallel.each(mm_resources, in_threads: THREADS_COUNT, progress: "Processing sitemap") { |mm_resource| add_local_resource(mm_resource) }
|
170
188
|
|
171
|
-
Parallel.each(remote_only_paths, in_threads:
|
189
|
+
Parallel.each(remote_only_paths, in_threads: THREADS_COUNT, progress: "Processing remote files") do |remote_path|
|
172
190
|
s3_sync_resources[remote_path] ||= S3Sync::Resource.new(nil, remote_resource_for_path(remote_path)).tap(&:status)
|
173
191
|
end
|
174
192
|
|
@@ -70,8 +70,10 @@ module Middleman
|
|
70
70
|
verbose = options[:verbose] ? 0 : 1
|
71
71
|
instrument = options[:instrument]
|
72
72
|
|
73
|
+
mode = options[:build] ? :build : :config
|
74
|
+
|
73
75
|
::Middleman::S3Sync.app = ::Middleman::Application.new do
|
74
|
-
config[:mode] =
|
76
|
+
config[:mode] = mode
|
75
77
|
config[:environment] = env
|
76
78
|
::Middleman::Logger.singleton(verbose, instrument)
|
77
79
|
end
|
@@ -9,9 +9,11 @@ module Middleman
|
|
9
9
|
option :http_prefix, nil, 'Path prefix of the resources'
|
10
10
|
option :acl, 'public-read', 'ACL for the resources being pushed to S3'
|
11
11
|
option :bucket, nil, 'The name of the bucket we are pushing to.'
|
12
|
+
option :endpoint, nil, 'The name of the endpoint to use - useful when using S3 compatible storage'
|
12
13
|
option :region, 'us-east-1', 'The name of the AWS region hosting the S3 bucket'
|
13
14
|
option :aws_access_key_id, ENV['AWS_ACCESS_KEY_ID'] , 'The AWS access key id'
|
14
15
|
option :aws_secret_access_key, ENV['AWS_SECRET_ACCESS_KEY'], 'The AWS secret access key'
|
16
|
+
option :aws_session_token, ENV['AWS_SESSION_TOKEN'] || ENV['AWS_SECURITY_TOKEN'], 'The AWS session token (for assuming roles)'
|
15
17
|
option :after_build, false, 'Whether to synchronize right after the build'
|
16
18
|
option :build_dir, nil, 'Where the built site is stored'
|
17
19
|
option :delete, true, 'Whether to delete resources that do not have a local equivalent'
|
@@ -26,6 +28,7 @@ module Middleman
|
|
26
28
|
option :index_document, nil, 'S3 custom index document path'
|
27
29
|
option :error_document, nil, 'S3 custom error document path'
|
28
30
|
option :content_types, {}, 'Custom content types'
|
31
|
+
option :ignore_paths, [], 'Paths that should be ignored during sync, strings or regex are allowed'
|
29
32
|
|
30
33
|
expose_to_config :s3_sync_options, :default_caching_policy, :caching_policy
|
31
34
|
|
@@ -40,6 +43,7 @@ module Middleman
|
|
40
43
|
read_config
|
41
44
|
options.aws_access_key_id ||= ENV['AWS_ACCESS_KEY_ID']
|
42
45
|
options.aws_secret_access_key ||= ENV['AWS_SECRET_ACCESS_KEY']
|
46
|
+
options.aws_session_token ||= ENV['AWS_SESSION_TOKEN'] || ENV['AWS_SECURITY_TOKEN']
|
43
47
|
options.bucket ||= ENV['AWS_BUCKET']
|
44
48
|
options.http_prefix = app.http_prefix if app.respond_to? :http_prefix
|
45
49
|
options.build_dir ||= app.build_dir if app.respond_to? :build_dir
|
@@ -54,8 +58,15 @@ module Middleman
|
|
54
58
|
::Middleman::S3Sync.sync() if options.after_build
|
55
59
|
end
|
56
60
|
|
57
|
-
def manipulate_resource_list(
|
58
|
-
::Middleman::S3Sync.mm_resources =
|
61
|
+
def manipulate_resource_list(resources)
|
62
|
+
::Middleman::S3Sync.mm_resources = resources.each_with_object([]) do |resource, list|
|
63
|
+
next if resource.ignored?
|
64
|
+
|
65
|
+
list << resource
|
66
|
+
list << resource.target_resource if resource.respond_to?(:target_resource)
|
67
|
+
end
|
68
|
+
|
69
|
+
resources
|
59
70
|
end
|
60
71
|
|
61
72
|
def s3_sync_options
|
@@ -73,7 +84,7 @@ module Middleman
|
|
73
84
|
config_file_path = File.join(root_path, ".s3_sync")
|
74
85
|
|
75
86
|
# skip if config file does not exist
|
76
|
-
return unless File.
|
87
|
+
return unless File.exist?(config_file_path)
|
77
88
|
|
78
89
|
io = File.open(config_file_path, "r")
|
79
90
|
end
|
data/lib/middleman-s3_sync.rb
CHANGED
data/middleman-s3_sync.gemspec
CHANGED
@@ -18,22 +18,25 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_runtime_dependency 'middleman-core'
|
21
|
+
gem.add_runtime_dependency 'middleman-core'
|
22
22
|
gem.add_runtime_dependency 'middleman-cli'
|
23
23
|
gem.add_runtime_dependency 'unf'
|
24
|
-
gem.add_runtime_dependency '
|
24
|
+
gem.add_runtime_dependency 'aws-sdk-s3'
|
25
25
|
gem.add_runtime_dependency 'map'
|
26
26
|
gem.add_runtime_dependency 'parallel'
|
27
27
|
gem.add_runtime_dependency 'ruby-progressbar'
|
28
28
|
gem.add_runtime_dependency 'ansi', '~> 1.5.0'
|
29
|
+
gem.add_runtime_dependency 'mime-types', '~> 3.1'
|
30
|
+
gem.add_runtime_dependency 'base64'
|
31
|
+
gem.add_runtime_dependency 'nokogiri', '>= 1.18.4'
|
29
32
|
|
30
33
|
gem.add_development_dependency 'rake'
|
31
34
|
gem.add_development_dependency 'pry'
|
32
35
|
gem.add_development_dependency 'pry-byebug'
|
33
|
-
gem.add_development_dependency 'rspec'
|
36
|
+
gem.add_development_dependency 'rspec'
|
37
|
+
gem.add_development_dependency 'rspec-support'
|
34
38
|
gem.add_development_dependency 'rspec-its'
|
35
39
|
gem.add_development_dependency 'rspec-mocks'
|
36
40
|
gem.add_development_dependency 'timerizer'
|
37
|
-
gem.add_development_dependency '
|
38
|
-
gem.add_development_dependency 'travis-lint'
|
41
|
+
gem.add_development_dependency 'webrick'
|
39
42
|
end
|
data/spec/resource_spec.rb
CHANGED
@@ -8,13 +8,39 @@ describe Middleman::S3Sync::Resource do
|
|
8
8
|
|
9
9
|
let(:mm_resource) {
|
10
10
|
double(
|
11
|
-
destination_path: 'path/to/resource.html'
|
11
|
+
destination_path: 'path/to/resource.html',
|
12
|
+
content_type: 'text/html'
|
12
13
|
)
|
13
14
|
}
|
15
|
+
|
16
|
+
let(:s3_client) { instance_double(Aws::S3::Client) }
|
17
|
+
let(:s3_resource) { instance_double(Aws::S3::Resource) }
|
18
|
+
let(:bucket) { instance_double(Aws::S3::Bucket) }
|
19
|
+
let(:s3_object) { instance_double(Aws::S3::Object) }
|
20
|
+
|
14
21
|
before do
|
15
22
|
Middleman::S3Sync.s3_sync_options = options
|
23
|
+
|
16
24
|
options.build_dir = "build"
|
17
25
|
options.prefer_gzip = false
|
26
|
+
options.bucket = "test-bucket"
|
27
|
+
options.acl = "public-read"
|
28
|
+
|
29
|
+
allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
|
30
|
+
allow(Aws::S3::Resource).to receive(:new).and_return(s3_resource)
|
31
|
+
allow(s3_resource).to receive(:bucket).and_return(bucket)
|
32
|
+
allow(bucket).to receive(:exists?).and_return(true)
|
33
|
+
allow(bucket).to receive(:object) do |path|
|
34
|
+
# Ensure path has no leading slash
|
35
|
+
path = path.sub(/^\//, '') if path.is_a?(String)
|
36
|
+
s3_object
|
37
|
+
end
|
38
|
+
allow(s3_object).to receive(:head).and_return(nil)
|
39
|
+
allow(s3_object).to receive(:put).and_return(true)
|
40
|
+
allow(s3_object).to receive(:delete).and_return(true)
|
41
|
+
|
42
|
+
# Allow Middleman::S3Sync to use our mocked bucket
|
43
|
+
allow(Middleman::S3Sync).to receive(:bucket).and_return(bucket)
|
18
44
|
end
|
19
45
|
|
20
46
|
context "a new resource" do
|
@@ -23,6 +49,7 @@ describe Middleman::S3Sync::Resource do
|
|
23
49
|
context "without a prefix" do
|
24
50
|
before do
|
25
51
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
52
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
26
53
|
end
|
27
54
|
|
28
55
|
its(:status) { is_expected.to eq :new }
|
@@ -31,11 +58,11 @@ describe Middleman::S3Sync::Resource do
|
|
31
58
|
expect(resource).not_to be_remote
|
32
59
|
end
|
33
60
|
|
34
|
-
it "
|
61
|
+
it "exists locally" do
|
35
62
|
expect(resource).to be_local
|
36
63
|
end
|
37
64
|
|
38
|
-
its(:path) { is_expected.to eq 'path/to/resource.html'}
|
65
|
+
its(:path) { is_expected.to eq 'path/to/resource.html' }
|
39
66
|
its(:local_path) { is_expected.to eq 'build/path/to/resource.html' }
|
40
67
|
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
41
68
|
end
|
@@ -43,6 +70,7 @@ describe Middleman::S3Sync::Resource do
|
|
43
70
|
context "with a prefix set" do
|
44
71
|
before do
|
45
72
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
73
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
46
74
|
options.prefix = "bob"
|
47
75
|
end
|
48
76
|
|
@@ -62,6 +90,9 @@ describe Middleman::S3Sync::Resource do
|
|
62
90
|
context "gzipped" do
|
63
91
|
before do
|
64
92
|
allow(File).to receive(:exist?).with('build/path/to/resource.html.gz').and_return(true)
|
93
|
+
allow(File).to receive(:read).with('build/path/to/resource.html.gz').and_return('gzipped content')
|
94
|
+
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(true)
|
95
|
+
allow(File).to receive(:read).with('build/path/to/resource.html').and_return('test content')
|
65
96
|
options.prefer_gzip = true
|
66
97
|
end
|
67
98
|
|
@@ -82,12 +113,7 @@ describe Middleman::S3Sync::Resource do
|
|
82
113
|
context "the file does not exist locally" do
|
83
114
|
subject(:resource) { Middleman::S3Sync::Resource.new(nil, remote) }
|
84
115
|
|
85
|
-
let(:remote) {
|
86
|
-
double(
|
87
|
-
key: 'path/to/resource.html',
|
88
|
-
metadata: {}
|
89
|
-
)
|
90
|
-
}
|
116
|
+
let(:remote) { mock_s3_object('path/to/resource.html') }
|
91
117
|
|
92
118
|
before do
|
93
119
|
resource.full_s3_resource = remote
|
@@ -103,7 +129,7 @@ describe Middleman::S3Sync::Resource do
|
|
103
129
|
expect(resource).to be_remote
|
104
130
|
end
|
105
131
|
|
106
|
-
it "
|
132
|
+
it "does not exist locally" do
|
107
133
|
expect(resource).not_to be_local
|
108
134
|
end
|
109
135
|
|
@@ -115,8 +141,9 @@ describe Middleman::S3Sync::Resource do
|
|
115
141
|
context "with a prefix set" do
|
116
142
|
before do
|
117
143
|
allow(File).to receive(:exist?).with('build/path/to/resource.html').and_return(false)
|
118
|
-
|
119
|
-
|
144
|
+
remote = mock_s3_object('bob/path/to/resource.html')
|
145
|
+
resource.full_s3_resource = remote
|
146
|
+
options.prefix = "bob/"
|
120
147
|
end
|
121
148
|
|
122
149
|
its(:status) { is_expected.to eq :deleted }
|
@@ -145,7 +172,7 @@ describe Middleman::S3Sync::Resource do
|
|
145
172
|
expect(resource).to be_remote
|
146
173
|
end
|
147
174
|
|
148
|
-
it "
|
175
|
+
it "does not exist locally" do
|
149
176
|
expect(resource).not_to be_local
|
150
177
|
end
|
151
178
|
|
@@ -154,4 +181,42 @@ describe Middleman::S3Sync::Resource do
|
|
154
181
|
its(:remote_path) { is_expected.to eq 'path/to/resource.html' }
|
155
182
|
end
|
156
183
|
end
|
184
|
+
|
185
|
+
context 'An ignored resource' do
|
186
|
+
context "that is local" do
|
187
|
+
|
188
|
+
subject(:resource) { Middleman::S3Sync::Resource.new(mm_resource, nil) }
|
189
|
+
|
190
|
+
let(:mm_resource) {
|
191
|
+
double(
|
192
|
+
destination_path: 'ignored/path/to/resource.html',
|
193
|
+
content_type: 'text/html'
|
194
|
+
)
|
195
|
+
}
|
196
|
+
|
197
|
+
before do
|
198
|
+
allow(File).to receive(:exist?).with('build/ignored/path/to/resource.html').and_return(true)
|
199
|
+
allow(File).to receive(:read).with('build/ignored/path/to/resource.html').and_return('test content')
|
200
|
+
options.ignore_paths = [/^ignored/]
|
201
|
+
end
|
202
|
+
|
203
|
+
its(:status) { is_expected.to eq :ignored }
|
204
|
+
end
|
205
|
+
|
206
|
+
context "that is remote" do
|
207
|
+
|
208
|
+
subject(:resource) { Middleman::S3Sync::Resource.new(nil, remote) }
|
209
|
+
|
210
|
+
let(:remote) { mock_s3_object('ignored/path/to/resource.html') }
|
211
|
+
|
212
|
+
before do
|
213
|
+
resource.full_s3_resource = remote
|
214
|
+
options.ignore_paths = [/^ignored/]
|
215
|
+
end
|
216
|
+
|
217
|
+
its(:status) { is_expected.to eq :ignored }
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
157
222
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,6 +8,9 @@
|
|
8
8
|
require 'middleman-s3_sync'
|
9
9
|
require 'timerizer'
|
10
10
|
require 'rspec/its'
|
11
|
+
require 'rspec/support'
|
12
|
+
require 'digest/md5'
|
13
|
+
require 'cgi'
|
11
14
|
|
12
15
|
RSpec.configure do |config|
|
13
16
|
config.run_all_when_everything_filtered = true
|
@@ -20,6 +23,43 @@ RSpec.configure do |config|
|
|
20
23
|
config.order = 'random'
|
21
24
|
|
22
25
|
config.before :all do
|
23
|
-
|
26
|
+
Aws.config.update(
|
27
|
+
region: 'us-east-1',
|
28
|
+
credentials: Aws::Credentials.new('access_key_id', 'secret_access_key'),
|
29
|
+
stub_responses: true
|
30
|
+
)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Helper method to create a mock S3 object
|
34
|
+
def mock_s3_object(key, metadata = {})
|
35
|
+
# Ensure key has no leading slash to match test expectations
|
36
|
+
key = key.sub(/^\//, '')
|
37
|
+
|
38
|
+
obj = double(
|
39
|
+
key: key,
|
40
|
+
metadata: metadata,
|
41
|
+
etag: "\"#{Digest::MD5.hexdigest('test content')}\"",
|
42
|
+
content_encoding: nil,
|
43
|
+
cache_control: nil,
|
44
|
+
website_redirect_location: nil
|
45
|
+
)
|
46
|
+
|
47
|
+
# Allow head method to return self for testing
|
48
|
+
allow(obj).to receive(:head).and_return(obj)
|
49
|
+
allow(obj).to receive(:put).and_return(true)
|
50
|
+
allow(obj).to receive(:delete).and_return(true)
|
51
|
+
|
52
|
+
obj
|
53
|
+
end
|
54
|
+
|
55
|
+
# Helper method to create a mock HeadObjectOutput
|
56
|
+
def mock_head_object(metadata = {})
|
57
|
+
double(
|
58
|
+
metadata: metadata,
|
59
|
+
etag: "\"#{Digest::MD5.hexdigest('test content')}\"",
|
60
|
+
content_encoding: nil,
|
61
|
+
cache_control: nil,
|
62
|
+
website_redirect_location: nil
|
63
|
+
)
|
24
64
|
end
|
25
65
|
end
|
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: middleman-s3_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.0
|
4
|
+
version: 4.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Frederic Jean
|
8
8
|
- Will Koehler
|
9
|
-
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2025-04-28 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: middleman-core
|
@@ -17,14 +16,14 @@ dependencies:
|
|
17
16
|
requirements:
|
18
17
|
- - ">="
|
19
18
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
19
|
+
version: '0'
|
21
20
|
type: :runtime
|
22
21
|
prerelease: false
|
23
22
|
version_requirements: !ruby/object:Gem::Requirement
|
24
23
|
requirements:
|
25
24
|
- - ">="
|
26
25
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
26
|
+
version: '0'
|
28
27
|
- !ruby/object:Gem::Dependency
|
29
28
|
name: middleman-cli
|
30
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -54,19 +53,19 @@ dependencies:
|
|
54
53
|
- !ruby/object:Gem::Version
|
55
54
|
version: '0'
|
56
55
|
- !ruby/object:Gem::Dependency
|
57
|
-
name:
|
56
|
+
name: aws-sdk-s3
|
58
57
|
requirement: !ruby/object:Gem::Requirement
|
59
58
|
requirements:
|
60
59
|
- - ">="
|
61
60
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0
|
61
|
+
version: '0'
|
63
62
|
type: :runtime
|
64
63
|
prerelease: false
|
65
64
|
version_requirements: !ruby/object:Gem::Requirement
|
66
65
|
requirements:
|
67
66
|
- - ">="
|
68
67
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0
|
68
|
+
version: '0'
|
70
69
|
- !ruby/object:Gem::Dependency
|
71
70
|
name: map
|
72
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,6 +122,48 @@ dependencies:
|
|
123
122
|
- - "~>"
|
124
123
|
- !ruby/object:Gem::Version
|
125
124
|
version: 1.5.0
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mime-types
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '3.1'
|
132
|
+
type: :runtime
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.1'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: base64
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :runtime
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: nokogiri
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 1.18.4
|
160
|
+
type: :runtime
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: 1.18.4
|
126
167
|
- !ruby/object:Gem::Dependency
|
127
168
|
name: rake
|
128
169
|
requirement: !ruby/object:Gem::Requirement
|
@@ -171,16 +212,16 @@ dependencies:
|
|
171
212
|
requirements:
|
172
213
|
- - ">="
|
173
214
|
- !ruby/object:Gem::Version
|
174
|
-
version:
|
215
|
+
version: '0'
|
175
216
|
type: :development
|
176
217
|
prerelease: false
|
177
218
|
version_requirements: !ruby/object:Gem::Requirement
|
178
219
|
requirements:
|
179
220
|
- - ">="
|
180
221
|
- !ruby/object:Gem::Version
|
181
|
-
version:
|
222
|
+
version: '0'
|
182
223
|
- !ruby/object:Gem::Dependency
|
183
|
-
name: rspec-
|
224
|
+
name: rspec-support
|
184
225
|
requirement: !ruby/object:Gem::Requirement
|
185
226
|
requirements:
|
186
227
|
- - ">="
|
@@ -194,7 +235,7 @@ dependencies:
|
|
194
235
|
- !ruby/object:Gem::Version
|
195
236
|
version: '0'
|
196
237
|
- !ruby/object:Gem::Dependency
|
197
|
-
name: rspec-
|
238
|
+
name: rspec-its
|
198
239
|
requirement: !ruby/object:Gem::Requirement
|
199
240
|
requirements:
|
200
241
|
- - ">="
|
@@ -208,7 +249,7 @@ dependencies:
|
|
208
249
|
- !ruby/object:Gem::Version
|
209
250
|
version: '0'
|
210
251
|
- !ruby/object:Gem::Dependency
|
211
|
-
name:
|
252
|
+
name: rspec-mocks
|
212
253
|
requirement: !ruby/object:Gem::Requirement
|
213
254
|
requirements:
|
214
255
|
- - ">="
|
@@ -222,7 +263,7 @@ dependencies:
|
|
222
263
|
- !ruby/object:Gem::Version
|
223
264
|
version: '0'
|
224
265
|
- !ruby/object:Gem::Dependency
|
225
|
-
name:
|
266
|
+
name: timerizer
|
226
267
|
requirement: !ruby/object:Gem::Requirement
|
227
268
|
requirements:
|
228
269
|
- - ">="
|
@@ -236,7 +277,7 @@ dependencies:
|
|
236
277
|
- !ruby/object:Gem::Version
|
237
278
|
version: '0'
|
238
279
|
- !ruby/object:Gem::Dependency
|
239
|
-
name:
|
280
|
+
name: webrick
|
240
281
|
requirement: !ruby/object:Gem::Requirement
|
241
282
|
requirements:
|
242
283
|
- - ">="
|
@@ -268,6 +309,7 @@ files:
|
|
268
309
|
- lib/middleman-s3_sync.rb
|
269
310
|
- lib/middleman-s3_sync/commands.rb
|
270
311
|
- lib/middleman-s3_sync/extension.rb
|
312
|
+
- lib/middleman/redirect.rb
|
271
313
|
- lib/middleman/s3_sync.rb
|
272
314
|
- lib/middleman/s3_sync/caching_policy.rb
|
273
315
|
- lib/middleman/s3_sync/options.rb
|
@@ -283,7 +325,6 @@ homepage: http://github.com/fredjean/middleman-s3_sync
|
|
283
325
|
licenses:
|
284
326
|
- MIT
|
285
327
|
metadata: {}
|
286
|
-
post_install_message:
|
287
328
|
rdoc_options: []
|
288
329
|
require_paths:
|
289
330
|
- lib
|
@@ -298,9 +339,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
298
339
|
- !ruby/object:Gem::Version
|
299
340
|
version: '0'
|
300
341
|
requirements: []
|
301
|
-
|
302
|
-
rubygems_version: 2.5.1
|
303
|
-
signing_key:
|
342
|
+
rubygems_version: 3.6.2
|
304
343
|
specification_version: 4
|
305
344
|
summary: Tries really, really hard not to push files to S3.
|
306
345
|
test_files:
|