middleman-s3_sync 4.6.2 → 4.6.3

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: 61870e8a5164a26db62b7463da7be2c74272800ab76cd6eab3fe83ec68e58081
4
- data.tar.gz: d901b5c020cc4fe8ddc695ceeb2fde34a3555f4645aca7ec04cea7d513a0e37c
3
+ metadata.gz: 575e3838865eccc6e585b35829b3891f2a86a77502df0da9e411634ad40312a1
4
+ data.tar.gz: bf897e1d7725bf536759469ef486dc81e51410c67f20b801322a1ad2403a8445
5
5
  SHA512:
6
- metadata.gz: 40ff859c4ae0b722c2132b3192bd14ab454988b612afb18f442645d7fbfb894b48e7d5ba502a1aae36439ec751c8f2f03e8496df255c736c5c2718770f69a7be
7
- data.tar.gz: 0a1fe315c71b79ae80041efaf2beb206cdc4caa1db49a7635ede51c8e53c8bbb1cd95a2b0a28cf2e9eb2dcc4c98101e5a6847c3792fc567d6f2b2d26c4bd92f4
6
+ metadata.gz: 1ef461a263b9ec005dcca856b34d3b02509550d2ba6d94904920bdf448268aacc547cbdf412406616f4ff409ce70e425ad013cd3e5e0de8a75315c1134d518e9
7
+ data.tar.gz: a00ae49ff314f27e8a3d6878ae00709a67c5759563675b64d72fdc5a61bdc057a19191ab04dea2795f80cfd75c86c00d0e99d707be382227c6c6eeabefeff2c6
data/.mise.toml ADDED
@@ -0,0 +1,17 @@
1
+ [tools]
2
+ ruby = "3.4"
3
+
4
+ [env]
5
+ _.file = ".env"
6
+ _.path = ["./bin"]
7
+
8
+ [tasks.binstubs]
9
+ description = "Generate binstubs for all gems"
10
+ run = "bundle binstubs --all"
11
+
12
+ [tasks.setup]
13
+ description = "Setup project dependencies and binstubs"
14
+ run = [
15
+ "bundle install",
16
+ "mise run binstubs"
17
+ ]
data/Changelog.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  The gem that tries really hard not to push files to S3.
4
4
 
5
+ ## v4.6.3
6
+ * Restrict incompatible map 8.x installation
7
+
5
8
  ## v4.6.2
6
9
 
7
10
  * Fix AWS SDK parameter format issues from Fog migration
@@ -10,10 +10,10 @@ module Middleman
10
10
  :region,
11
11
  :aws_access_key_id,
12
12
  :aws_secret_access_key,
13
+ :aws_session_token,
13
14
  :after_build,
14
15
  :delete,
15
16
  :encryption,
16
- :existing_remote_file,
17
17
  :build_dir,
18
18
  :force,
19
19
  :prefer_gzip,
@@ -25,12 +25,35 @@ module Middleman
25
25
  :content_types,
26
26
  :ignore_paths,
27
27
  :index_document,
28
- :error_document
28
+ :error_document,
29
+ :cloudfront_distribution_id,
30
+ :cloudfront_invalidate,
31
+ :cloudfront_invalidate_all,
32
+ :cloudfront_invalidation_batch_size,
33
+ :cloudfront_invalidation_max_retries,
34
+ :cloudfront_invalidation_batch_delay,
35
+ :cloudfront_wait
29
36
  ]
30
37
  attr_accessor *OPTIONS
31
38
 
32
39
  def acl
33
- @acl || 'public-read'
40
+ # If @acl is explicitly set to empty string or false, return nil (for buckets with ACLs disabled)
41
+ # If @acl is nil and was never set, return default 'public-read'
42
+ # Otherwise return the set value
43
+ return nil if @acl == '' || @acl == false
44
+ @acl_explicitly_set ? @acl : (@acl || 'public-read')
45
+ end
46
+
47
+ def acl=(value)
48
+ @acl_explicitly_set = true
49
+ @acl = value
50
+ end
51
+
52
+ def acl_enabled?
53
+ # ACLs are disabled if explicitly set to nil, empty string, or false
54
+ return false if @acl_explicitly_set && (@acl.nil? || @acl == '' || @acl == false)
55
+ # Otherwise ACLs are enabled (using default or explicit value)
56
+ true
34
57
  end
35
58
 
36
59
  def aws_access_key_id=(aws_access_key_id)
@@ -55,10 +55,11 @@ module Middleman
55
55
  def to_h
56
56
  attributes = {
57
57
  :key => key,
58
- :acl => options.acl,
59
58
  :content_type => content_type,
60
59
  'content-md5' => local_content_md5
61
60
  }
61
+ # Only add ACL if enabled (not for buckets with ACLs disabled)
62
+ attributes[:acl] = options.acl if options.acl_enabled?
62
63
 
63
64
  if caching_policy
64
65
  attributes[:cache_control] = caching_policy.cache_control
@@ -115,40 +116,22 @@ module Middleman
115
116
 
116
117
  def upload!
117
118
  object = bucket.object(remote_path.sub(/^\//, ''))
118
- upload_options = {
119
- body: local_content,
120
- content_type: content_type,
121
- acl: options.acl
122
- }
123
-
124
- # Add metadata if present
125
- if local_content_md5
126
- upload_options[:metadata] = { 'content-md5' => local_content_md5 }
127
- end
128
-
129
- # Add redirect if present
130
- upload_options[:website_redirect_location] = redirect_url if redirect?
131
-
132
- # Add content encoding if present
133
- upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped
134
-
135
- # Add cache control and expires if present
136
- if caching_policy
137
- upload_options[:cache_control] = caching_policy.cache_control
138
- upload_options[:expires] = caching_policy.expires
139
- end
140
-
141
- # Add storage class if needed
142
- if options.reduced_redundancy_storage
143
- upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
144
- end
145
-
146
- # Add encryption if needed
147
- if options.encryption
148
- upload_options[:server_side_encryption] = 'AES256'
119
+ upload_options = build_upload_options
120
+
121
+ begin
122
+ object.put(upload_options)
123
+ rescue Aws::S3::Errors::AccessControlListNotSupported => e
124
+ # Bucket has ACLs disabled - retry without ACL
125
+ if upload_options.key?(:acl)
126
+ say_status "#{ANSI.yellow{"Note"}} Bucket does not support ACLs, retrying without ACL parameter"
127
+ # Automatically disable ACLs for this bucket going forward
128
+ options.acl = ''
129
+ upload_options.delete(:acl)
130
+ retry
131
+ else
132
+ raise e
133
+ end
149
134
  end
150
-
151
- object.put(upload_options)
152
135
  end
153
136
 
154
137
  def ignore!
@@ -330,6 +313,45 @@ module Middleman
330
313
  end
331
314
 
332
315
  protected
316
+
317
+ def build_upload_options
318
+ upload_options = {
319
+ body: local_content,
320
+ content_type: content_type
321
+ }
322
+ # Only add ACL if enabled (not for buckets with ACLs disabled)
323
+ upload_options[:acl] = options.acl if options.acl_enabled?
324
+
325
+ # Add metadata if present
326
+ if local_content_md5
327
+ upload_options[:metadata] = { 'content-md5' => local_content_md5 }
328
+ end
329
+
330
+ # Add redirect if present
331
+ upload_options[:website_redirect_location] = redirect_url if redirect?
332
+
333
+ # Add content encoding if present
334
+ upload_options[:content_encoding] = "gzip" if options.prefer_gzip && gzipped
335
+
336
+ # Add cache control and expires if present
337
+ if caching_policy
338
+ upload_options[:cache_control] = caching_policy.cache_control
339
+ upload_options[:expires] = caching_policy.expires
340
+ end
341
+
342
+ # Add storage class if needed
343
+ if options.reduced_redundancy_storage
344
+ upload_options[:storage_class] = 'REDUCED_REDUNDANCY'
345
+ end
346
+
347
+ # Add encryption if needed
348
+ if options.encryption
349
+ upload_options[:server_side_encryption] = 'AES256'
350
+ end
351
+
352
+ upload_options
353
+ end
354
+
333
355
  def bucket
334
356
  Middleman::S3Sync.bucket
335
357
  end
@@ -1,5 +1,5 @@
1
1
  module Middleman
2
2
  module S3Sync
3
- VERSION = "4.6.2"
3
+ VERSION = "4.6.3"
4
4
  end
5
5
  end
@@ -77,7 +77,28 @@ module Middleman
77
77
  end
78
78
 
79
79
  def s3_sync_options
80
- options
80
+ self
81
+ end
82
+
83
+ def acl_enabled?
84
+ # ACLs are disabled if acl is explicitly set to nil, empty string, or false
85
+ acl_value = options.acl
86
+ return false if acl_value.nil? || acl_value == '' || acl_value == false
87
+ # Otherwise ACLs are enabled (using default or explicit value)
88
+ true
89
+ end
90
+
91
+ # Delegate option readers to the options object
92
+ def method_missing(method, *args, &block)
93
+ if options.respond_to?(method)
94
+ options.send(method, *args, &block)
95
+ else
96
+ super
97
+ end
98
+ end
99
+
100
+ def respond_to_missing?(method, include_private = false)
101
+ options.respond_to?(method) || super
81
102
  end
82
103
 
83
104
  # Read config options from an IO stream and set them on `self`. Defaults
@@ -20,15 +20,13 @@ Gem::Specification.new do |gem|
20
20
 
21
21
  gem.add_runtime_dependency 'middleman-core'
22
22
  gem.add_runtime_dependency 'middleman-cli'
23
- gem.add_runtime_dependency 'unf'
24
- gem.add_runtime_dependency 'aws-sdk-s3'
23
+ gem.add_runtime_dependency 'aws-sdk-s3', '>= 1.187.0'
25
24
  gem.add_runtime_dependency 'aws-sdk-cloudfront'
26
- gem.add_runtime_dependency 'map'
25
+ gem.add_runtime_dependency 'map', '6.6.0'
27
26
  gem.add_runtime_dependency 'parallel'
28
27
  gem.add_runtime_dependency 'ruby-progressbar'
29
28
  gem.add_runtime_dependency 'ansi', '~> 1.5.0'
30
29
  gem.add_runtime_dependency 'mime-types', '~> 3.1'
31
- gem.add_runtime_dependency 'base64'
32
30
  gem.add_runtime_dependency 'nokogiri', '>= 1.18.4'
33
31
 
34
32
  gem.add_development_dependency 'rake'
@@ -152,6 +152,88 @@ describe 'AWS SDK Parameter Validation' do
152
152
  resource.upload!
153
153
  end
154
154
 
155
+ context 'when ACL is set to empty string (for buckets with ACLs disabled)' do
156
+ before do
157
+ options.acl = ''
158
+ end
159
+
160
+ it 'does not include acl parameter in upload' do
161
+ expect(s3_object).to receive(:put) do |upload_options|
162
+ expect(upload_options).not_to have_key(:acl)
163
+ expect(upload_options[:body]).to eq('test content')
164
+ expect(upload_options[:content_type]).to eq('text/html')
165
+ end
166
+
167
+ resource.upload!
168
+ end
169
+ end
170
+
171
+ context 'when ACL is set to nil (for buckets with ACLs disabled)' do
172
+ before do
173
+ options.acl = nil
174
+ end
175
+
176
+ it 'does not include acl parameter in upload' do
177
+ expect(s3_object).to receive(:put) do |upload_options|
178
+ expect(upload_options).not_to have_key(:acl)
179
+ expect(upload_options[:body]).to eq('test content')
180
+ expect(upload_options[:content_type]).to eq('text/html')
181
+ end
182
+
183
+ resource.upload!
184
+ end
185
+ end
186
+
187
+ context 'when bucket does not support ACLs (auto-detection)' do
188
+ before do
189
+ # ACL is enabled by default
190
+ expect(options.acl).to eq('public-read')
191
+ end
192
+
193
+ it 'automatically retries without ACL when AccessControlListNotSupported error occurs' do
194
+ call_count = 0
195
+ expect(s3_object).to receive(:put).twice do |upload_options|
196
+ call_count += 1
197
+ if call_count == 1
198
+ # First call should include ACL
199
+ expect(upload_options[:acl]).to eq('public-read')
200
+ raise Aws::S3::Errors::AccessControlListNotSupported.new(nil, 'The bucket does not allow ACLs')
201
+ else
202
+ # Second call should not include ACL
203
+ expect(upload_options).not_to have_key(:acl)
204
+ expect(upload_options[:body]).to eq('test content')
205
+ expect(upload_options[:content_type]).to eq('text/html')
206
+ end
207
+ end
208
+
209
+ # Should automatically disable ACLs after the error
210
+ resource.upload!
211
+ expect(options.acl_enabled?).to be false
212
+ end
213
+
214
+ it 'permanently disables ACLs after detecting bucket does not support them' do
215
+ call_count = 0
216
+ allow(s3_object).to receive(:put) do |upload_options|
217
+ call_count += 1
218
+ if call_count == 1
219
+ expect(upload_options[:acl]).to eq('public-read')
220
+ raise Aws::S3::Errors::AccessControlListNotSupported.new(nil, 'The bucket does not allow ACLs')
221
+ else
222
+ # Second call should succeed without ACL
223
+ expect(upload_options).not_to have_key(:acl)
224
+ true
225
+ end
226
+ end
227
+
228
+ resource.upload!
229
+ expect(options.acl_enabled?).to be false
230
+
231
+ # Verify ACLs stay disabled for future uploads
232
+ resource.upload!
233
+ expect(call_count).to eq(3) # First attempt, retry, and third upload
234
+ end
235
+ end
236
+
155
237
  context 'when gzip is enabled' do
156
238
  before do
157
239
  options.prefer_gzip = true
@@ -306,6 +388,34 @@ describe 'AWS SDK Parameter Validation' do
306
388
  expect(attributes).not_to have_key('x-amz-meta-content-md5')
307
389
  end
308
390
 
391
+ context 'when ACL is set to empty string' do
392
+ before do
393
+ options.acl = ''
394
+ end
395
+
396
+ it 'does not include acl in attributes' do
397
+ attributes = resource.to_h
398
+
399
+ expect(attributes).not_to have_key(:acl)
400
+ expect(attributes[:key]).to eq('test/file.html')
401
+ expect(attributes[:content_type]).to eq('text/html')
402
+ end
403
+ end
404
+
405
+ context 'when ACL is set to nil' do
406
+ before do
407
+ options.acl = nil
408
+ end
409
+
410
+ it 'does not include acl in attributes' do
411
+ attributes = resource.to_h
412
+
413
+ expect(attributes).not_to have_key(:acl)
414
+ expect(attributes[:key]).to eq('test/file.html')
415
+ expect(attributes[:content_type]).to eq('text/html')
416
+ end
417
+ end
418
+
309
419
  context 'when resource has a redirect' do
310
420
  let(:mm_resource) do
311
421
  double(
@@ -0,0 +1,114 @@
1
+ require 'spec_helper'
2
+
3
+ describe Middleman::S3SyncExtension do
4
+ let(:app) { double('app').as_null_object }
5
+
6
+ let(:extension) { described_class.new(app, {}) }
7
+
8
+ describe '#acl_enabled?' do
9
+ context 'when acl has default value' do
10
+ it 'returns true' do
11
+ expect(extension.acl_enabled?).to be true
12
+ end
13
+ end
14
+
15
+ context 'when acl is explicitly set to a value' do
16
+ let(:extension) { described_class.new(app, acl: 'private') }
17
+
18
+ it 'returns true' do
19
+ expect(extension.acl_enabled?).to be true
20
+ end
21
+ end
22
+
23
+ context 'when acl is set to nil' do
24
+ let(:extension) { described_class.new(app, acl: nil) }
25
+
26
+ it 'returns false' do
27
+ expect(extension.acl_enabled?).to be false
28
+ end
29
+ end
30
+
31
+ context 'when acl is set to empty string' do
32
+ let(:extension) { described_class.new(app, acl: '') }
33
+
34
+ it 'returns false' do
35
+ expect(extension.acl_enabled?).to be false
36
+ end
37
+ end
38
+
39
+ context 'when acl is set to false' do
40
+ let(:extension) { described_class.new(app, acl: false) }
41
+
42
+ it 'returns false' do
43
+ expect(extension.acl_enabled?).to be false
44
+ end
45
+ end
46
+ end
47
+
48
+ describe '#s3_sync_options' do
49
+ it 'returns the extension instance itself' do
50
+ expect(extension.s3_sync_options).to eq(extension)
51
+ end
52
+
53
+ it 'allows access to acl_enabled? through s3_sync_options' do
54
+ expect(extension.s3_sync_options.acl_enabled?).to be true
55
+ end
56
+ end
57
+
58
+ describe 'option delegation' do
59
+ let(:extension) do
60
+ described_class.new(app,
61
+ bucket: 'test-bucket',
62
+ region: 'us-west-2',
63
+ acl: 'public-read',
64
+ verbose: true
65
+ )
66
+ end
67
+
68
+ it 'delegates option readers to the options object' do
69
+ expect(extension.bucket).to eq('test-bucket')
70
+ expect(extension.region).to eq('us-west-2')
71
+ expect(extension.acl).to eq('public-read')
72
+ expect(extension.verbose).to be true
73
+ end
74
+
75
+ it 'responds to option methods' do
76
+ expect(extension.respond_to?(:bucket)).to be true
77
+ expect(extension.respond_to?(:region)).to be true
78
+ expect(extension.respond_to?(:acl)).to be true
79
+ expect(extension.respond_to?(:verbose)).to be true
80
+ end
81
+
82
+ it 'does not respond to non-existent methods' do
83
+ expect(extension.respond_to?(:nonexistent_method)).to be false
84
+ end
85
+
86
+ it 'raises NoMethodError for non-existent methods' do
87
+ expect { extension.nonexistent_method }.to raise_error(NoMethodError)
88
+ end
89
+ end
90
+
91
+ describe 'integration with S3Sync module' do
92
+ before do
93
+ allow(Middleman::Application).to receive(:root).and_return('/tmp')
94
+ allow(File).to receive(:exist?).with('/tmp/.s3_sync').and_return(false)
95
+ end
96
+
97
+ let(:extension) do
98
+ described_class.new(app,
99
+ bucket: 'test-bucket',
100
+ acl: 'private'
101
+ )
102
+ end
103
+
104
+ it 'sets s3_sync_options to the extension instance' do
105
+ extension.after_configuration
106
+ expect(Middleman::S3Sync.s3_sync_options).to eq(extension)
107
+ end
108
+
109
+ it 'allows S3Sync to access acl_enabled? through s3_sync_options' do
110
+ extension.after_configuration
111
+ expect(Middleman::S3Sync.s3_sync_options.acl_enabled?).to be true
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,272 @@
1
+ require 'spec_helper'
2
+
3
+ describe Middleman::S3Sync::Options do
4
+ let(:options) { described_class.new }
5
+
6
+ describe 'option accessors' do
7
+ it 'provides accessors for all defined options' do
8
+ described_class::OPTIONS.each do |option_name|
9
+ expect(options).to respond_to(option_name)
10
+ expect(options).to respond_to("#{option_name}=")
11
+ end
12
+ end
13
+ end
14
+
15
+ describe 'AWS credentials options' do
16
+ it 'supports aws_access_key_id' do
17
+ options.aws_access_key_id = 'test_key'
18
+ expect(options.aws_access_key_id).to eq('test_key')
19
+ end
20
+
21
+ it 'supports aws_secret_access_key' do
22
+ options.aws_secret_access_key = 'test_secret'
23
+ expect(options.aws_secret_access_key).to eq('test_secret')
24
+ end
25
+
26
+ it 'supports aws_session_token' do
27
+ options.aws_session_token = 'test_token'
28
+ expect(options.aws_session_token).to eq('test_token')
29
+ end
30
+
31
+ it 'falls back to ENV for aws_access_key_id' do
32
+ ENV['AWS_ACCESS_KEY_ID'] = 'env_key'
33
+ expect(options.aws_access_key_id).to eq('env_key')
34
+ ensure
35
+ ENV.delete('AWS_ACCESS_KEY_ID')
36
+ end
37
+
38
+ it 'falls back to ENV for aws_secret_access_key' do
39
+ ENV['AWS_SECRET_ACCESS_KEY'] = 'env_secret'
40
+ expect(options.aws_secret_access_key).to eq('env_secret')
41
+ ensure
42
+ ENV.delete('AWS_SECRET_ACCESS_KEY')
43
+ end
44
+ end
45
+
46
+ describe 'CloudFront options' do
47
+ it 'supports cloudfront_distribution_id' do
48
+ options.cloudfront_distribution_id = 'E1234567890123'
49
+ expect(options.cloudfront_distribution_id).to eq('E1234567890123')
50
+ end
51
+
52
+ it 'supports cloudfront_invalidate' do
53
+ options.cloudfront_invalidate = true
54
+ expect(options.cloudfront_invalidate).to be true
55
+ end
56
+
57
+ it 'supports cloudfront_invalidate_all' do
58
+ options.cloudfront_invalidate_all = true
59
+ expect(options.cloudfront_invalidate_all).to be true
60
+ end
61
+
62
+ it 'supports cloudfront_invalidation_batch_size' do
63
+ options.cloudfront_invalidation_batch_size = 500
64
+ expect(options.cloudfront_invalidation_batch_size).to eq(500)
65
+ end
66
+
67
+ it 'supports cloudfront_invalidation_max_retries' do
68
+ options.cloudfront_invalidation_max_retries = 10
69
+ expect(options.cloudfront_invalidation_max_retries).to eq(10)
70
+ end
71
+
72
+ it 'supports cloudfront_invalidation_batch_delay' do
73
+ options.cloudfront_invalidation_batch_delay = 5
74
+ expect(options.cloudfront_invalidation_batch_delay).to eq(5)
75
+ end
76
+
77
+ it 'supports cloudfront_wait' do
78
+ options.cloudfront_wait = true
79
+ expect(options.cloudfront_wait).to be true
80
+ end
81
+ end
82
+
83
+ describe 'S3 options' do
84
+ it 'supports bucket' do
85
+ options.bucket = 'my-bucket'
86
+ expect(options.bucket).to eq('my-bucket')
87
+ end
88
+
89
+ it 'supports region' do
90
+ options.region = 'us-west-2'
91
+ expect(options.region).to eq('us-west-2')
92
+ end
93
+
94
+ it 'supports endpoint' do
95
+ options.endpoint = 'https://s3-compatible.example.com'
96
+ expect(options.endpoint).to eq('https://s3-compatible.example.com')
97
+ end
98
+
99
+ it 'supports prefix' do
100
+ options.prefix = 'my-prefix'
101
+ expect(options.prefix).to eq('my-prefix/')
102
+ end
103
+
104
+ it 'supports path_style' do
105
+ options.path_style = false
106
+ expect(options.path_style).to be false
107
+ end
108
+
109
+ it 'defaults path_style to true' do
110
+ expect(options.path_style).to be true
111
+ end
112
+
113
+ it 'supports encryption' do
114
+ options.encryption = true
115
+ expect(options.encryption).to be true
116
+ end
117
+
118
+ it 'defaults encryption to false' do
119
+ expect(options.encryption).to be false
120
+ end
121
+
122
+ it 'supports reduced_redundancy_storage' do
123
+ options.reduced_redundancy_storage = true
124
+ expect(options.reduced_redundancy_storage).to be true
125
+ end
126
+ end
127
+
128
+ describe 'ACL options' do
129
+ it 'supports acl' do
130
+ options.acl = 'private'
131
+ expect(options.acl).to eq('private')
132
+ end
133
+
134
+ it 'defaults acl to public-read' do
135
+ expect(options.acl).to eq('public-read')
136
+ end
137
+
138
+ it 'supports acl_enabled?' do
139
+ expect(options.acl_enabled?).to be true
140
+ end
141
+
142
+ context 'when acl is disabled' do
143
+ it 'returns false for acl_enabled? when set to empty string' do
144
+ options.acl = ''
145
+ expect(options.acl_enabled?).to be false
146
+ end
147
+
148
+ it 'returns false for acl_enabled? when set to nil' do
149
+ options.acl = nil
150
+ expect(options.acl_enabled?).to be false
151
+ end
152
+
153
+ it 'returns false for acl_enabled? when set to false' do
154
+ options.acl = false
155
+ expect(options.acl_enabled?).to be false
156
+ end
157
+ end
158
+ end
159
+
160
+ describe 'sync behavior options' do
161
+ it 'supports delete' do
162
+ options.delete = false
163
+ expect(options.delete).to be false
164
+ end
165
+
166
+ it 'defaults delete to true' do
167
+ expect(options.delete).to be true
168
+ end
169
+
170
+ it 'supports force' do
171
+ options.force = true
172
+ expect(options.force).to be true
173
+ end
174
+
175
+ it 'supports prefer_gzip' do
176
+ options.prefer_gzip = false
177
+ expect(options.prefer_gzip).to be false
178
+ end
179
+
180
+ it 'defaults prefer_gzip to true' do
181
+ expect(options.prefer_gzip).to be true
182
+ end
183
+
184
+ it 'supports verbose' do
185
+ options.verbose = true
186
+ expect(options.verbose).to be true
187
+ end
188
+
189
+ it 'supports dry_run' do
190
+ options.dry_run = true
191
+ expect(options.dry_run).to be true
192
+ end
193
+
194
+ it 'supports version_bucket' do
195
+ options.version_bucket = true
196
+ expect(options.version_bucket).to be true
197
+ end
198
+
199
+ it 'defaults version_bucket to false' do
200
+ expect(options.version_bucket).to be false
201
+ end
202
+ end
203
+
204
+ describe 'build options' do
205
+ it 'supports build_dir' do
206
+ options.build_dir = 'dist'
207
+ expect(options.build_dir).to eq('dist')
208
+ end
209
+
210
+ it 'supports after_build' do
211
+ options.after_build = true
212
+ expect(options.after_build).to be true
213
+ end
214
+
215
+ it 'defaults after_build to false' do
216
+ expect(options.after_build).to be false
217
+ end
218
+ end
219
+
220
+ describe 'content options' do
221
+ it 'supports content_types' do
222
+ content_types = { '.webp' => 'image/webp' }
223
+ options.content_types = content_types
224
+ expect(options.content_types).to eq(content_types)
225
+ end
226
+
227
+ it 'supports ignore_paths' do
228
+ ignore_paths = [/\.bak$/, 'temp/']
229
+ options.ignore_paths = ignore_paths
230
+ expect(options.ignore_paths).to eq(ignore_paths)
231
+ end
232
+
233
+ it 'defaults ignore_paths to empty array' do
234
+ expect(options.ignore_paths).to eq([])
235
+ end
236
+ end
237
+
238
+ describe 'website options' do
239
+ it 'supports index_document' do
240
+ options.index_document = 'index.html'
241
+ expect(options.index_document).to eq('index.html')
242
+ end
243
+
244
+ it 'supports error_document' do
245
+ options.error_document = '404.html'
246
+ expect(options.error_document).to eq('404.html')
247
+ end
248
+ end
249
+
250
+ describe 'option consistency' do
251
+ it 'includes all options from extension in OPTIONS constant' do
252
+ # These are the options defined in the extension
253
+ extension_options = [
254
+ :prefix, :http_prefix, :acl, :bucket, :endpoint, :region,
255
+ :aws_access_key_id, :aws_secret_access_key, :aws_session_token,
256
+ :after_build, :build_dir, :delete, :encryption, :force,
257
+ :prefer_gzip, :reduced_redundancy_storage, :path_style,
258
+ :version_bucket, :verbose, :dry_run, :index_document,
259
+ :error_document, :content_types, :ignore_paths,
260
+ :cloudfront_distribution_id, :cloudfront_invalidate,
261
+ :cloudfront_invalidate_all, :cloudfront_invalidation_batch_size,
262
+ :cloudfront_invalidation_max_retries, :cloudfront_invalidation_batch_delay,
263
+ :cloudfront_wait
264
+ ]
265
+
266
+ extension_options.each do |option_name|
267
+ expect(described_class::OPTIONS).to include(option_name),
268
+ "Expected OPTIONS to include :#{option_name}"
269
+ end
270
+ end
271
+ end
272
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: middleman-s3_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.6.2
4
+ version: 4.6.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frederic Jean
8
8
  - Will Koehler
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-09-27 00:00:00.000000000 Z
11
+ date: 2026-01-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: middleman-core
@@ -38,34 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: unf
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: aws-sdk-s3
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
45
  - - ">="
60
46
  - !ruby/object:Gem::Version
61
- version: '0'
47
+ version: 1.187.0
62
48
  type: :runtime
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
52
  - - ">="
67
53
  - !ruby/object:Gem::Version
68
- version: '0'
54
+ version: 1.187.0
69
55
  - !ruby/object:Gem::Dependency
70
56
  name: aws-sdk-cloudfront
71
57
  requirement: !ruby/object:Gem::Requirement
@@ -84,16 +70,16 @@ dependencies:
84
70
  name: map
85
71
  requirement: !ruby/object:Gem::Requirement
86
72
  requirements:
87
- - - ">="
73
+ - - '='
88
74
  - !ruby/object:Gem::Version
89
- version: '0'
75
+ version: 6.6.0
90
76
  type: :runtime
91
77
  prerelease: false
92
78
  version_requirements: !ruby/object:Gem::Requirement
93
79
  requirements:
94
- - - ">="
80
+ - - '='
95
81
  - !ruby/object:Gem::Version
96
- version: '0'
82
+ version: 6.6.0
97
83
  - !ruby/object:Gem::Dependency
98
84
  name: parallel
99
85
  requirement: !ruby/object:Gem::Requirement
@@ -150,20 +136,6 @@ dependencies:
150
136
  - - "~>"
151
137
  - !ruby/object:Gem::Version
152
138
  version: '3.1'
153
- - !ruby/object:Gem::Dependency
154
- name: base64
155
- requirement: !ruby/object:Gem::Requirement
156
- requirements:
157
- - - ">="
158
- - !ruby/object:Gem::Version
159
- version: '0'
160
- type: :runtime
161
- prerelease: false
162
- version_requirements: !ruby/object:Gem::Requirement
163
- requirements:
164
- - - ">="
165
- - !ruby/object:Gem::Version
166
- version: '0'
167
139
  - !ruby/object:Gem::Dependency
168
140
  name: nokogiri
169
141
  requirement: !ruby/object:Gem::Requirement
@@ -311,8 +283,9 @@ executables: []
311
283
  extensions: []
312
284
  extra_rdoc_files: []
313
285
  files:
314
- - ".envrc"
286
+ - ".envrc.backup"
315
287
  - ".gitignore"
288
+ - ".mise.toml"
316
289
  - ".rspec"
317
290
  - ".s3_sync.sample"
318
291
  - ".travis.yml"
@@ -338,6 +311,8 @@ files:
338
311
  - spec/aws_sdk_parameters_spec.rb
339
312
  - spec/caching_policy_spec.rb
340
313
  - spec/cloudfront_spec.rb
314
+ - spec/extension_spec.rb
315
+ - spec/options_spec.rb
341
316
  - spec/resource_spec.rb
342
317
  - spec/s3_sync_integration_spec.rb
343
318
  - spec/spec_helper.rb
@@ -366,6 +341,8 @@ test_files:
366
341
  - spec/aws_sdk_parameters_spec.rb
367
342
  - spec/caching_policy_spec.rb
368
343
  - spec/cloudfront_spec.rb
344
+ - spec/extension_spec.rb
345
+ - spec/options_spec.rb
369
346
  - spec/resource_spec.rb
370
347
  - spec/s3_sync_integration_spec.rb
371
348
  - spec/spec_helper.rb
File without changes