middleman-s3_sync 4.6.2 → 4.6.4

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: e6b576cfe91e75975edadd71f0cfd319e84772edecd882aabe2e3c8c06c90082
4
+ data.tar.gz: 86bc102e074f9b47980e4cbf93060babd7f7b8a5117cbf35b60ea78ba7476ff0
5
5
  SHA512:
6
- metadata.gz: 40ff859c4ae0b722c2132b3192bd14ab454988b612afb18f442645d7fbfb894b48e7d5ba502a1aae36439ec751c8f2f03e8496df255c736c5c2718770f69a7be
7
- data.tar.gz: 0a1fe315c71b79ae80041efaf2beb206cdc4caa1db49a7635ede51c8e53c8bbb1cd95a2b0a28cf2e9eb2dcc4c98101e5a6847c3792fc567d6f2b2d26c4bd92f4
6
+ metadata.gz: 20b465799e297cff4e8085ba9c9e25637a25a3a77242d862f1d2b93291dd6112c32835fc91eb8fd92d0e4cd11df6676c034330d804e4556b5fc0ba4c2312215e
7
+ data.tar.gz: 36a4b96fa0c6831047a90ef4140c598bfab3eb5279fffa09adeee5e23673cb30a811806019ea63de16d1c52383b9a644a2ed6cf0bf400ccd1e9eaade616ae00a
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,14 @@
2
2
 
3
3
  The gem that tries really hard not to push files to S3.
4
4
 
5
+ ## v4.6.4
6
+ * Remove map gem dependency and replace with native Ruby implementation
7
+ * Add IndifferentHash class to provide string/symbol indifferent access without external dependencies
8
+ * Improve gem stability by eliminating dependency on unmaintained library
9
+
10
+ ## v4.6.3
11
+ * Restrict incompatible map 8.x installation
12
+
5
13
  ## v4.6.2
6
14
 
7
15
  * Fix AWS SDK parameter format issues from Fog migration
@@ -1,4 +1,4 @@
1
- require 'map'
1
+ require 'middleman/s3_sync/indifferent_hash'
2
2
 
3
3
  module Middleman
4
4
  module S3Sync
@@ -17,7 +17,7 @@ module Middleman
17
17
  end
18
18
 
19
19
  def caching_policies
20
- @caching_policies ||= Map.new
20
+ @caching_policies ||= IndifferentHash.new
21
21
  end
22
22
  end
23
23
 
@@ -25,7 +25,7 @@ module Middleman
25
25
  attr_accessor :policies
26
26
 
27
27
  def initialize(options = {})
28
- @policies = Map.from_hash(options)
28
+ @policies = IndifferentHash.from_hash(options)
29
29
  end
30
30
 
31
31
  def cache_control
@@ -0,0 +1,62 @@
1
+ module Middleman
2
+ module S3Sync
3
+ # A simple hash wrapper that provides string/symbol indifferent access
4
+ # This replaces the Map gem dependency with native Ruby functionality
5
+ class IndifferentHash < Hash
6
+ # Convert keys to strings for consistent access
7
+ def normalize_key(key)
8
+ key.to_s
9
+ end
10
+
11
+ # Override [] to provide indifferent access
12
+ def [](key)
13
+ super(normalize_key(key))
14
+ end
15
+
16
+ # Override []= to store with normalized keys
17
+ def []=(key, value)
18
+ super(normalize_key(key), value)
19
+ end
20
+
21
+ # Override fetch to provide indifferent access
22
+ def fetch(key, *args, &block)
23
+ super(normalize_key(key), *args, &block)
24
+ end
25
+
26
+ # Override has_key? to work with normalized keys
27
+ def has_key?(key)
28
+ super(normalize_key(key))
29
+ end
30
+ alias_method :key?, :has_key?
31
+ alias_method :include?, :has_key?
32
+
33
+ # Create an IndifferentHash from a regular hash
34
+ def self.from_hash(hash)
35
+ new_hash = new
36
+ hash.each do |key, value|
37
+ new_hash[key] = value
38
+ end
39
+ new_hash
40
+ end
41
+
42
+ # Provide dot notation access to hash values
43
+ def method_missing(method, *args, &block)
44
+ key = method.to_s
45
+ if key.end_with?('=')
46
+ # Handle setter: hash.key = value
47
+ self[key.chop] = args.first
48
+ elsif has_key?(key)
49
+ # Handle getter: hash.key
50
+ self[key]
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def respond_to_missing?(method, include_private = false)
57
+ key = method.to_s.sub(/=$/, '')
58
+ has_key?(key) || super
59
+ end
60
+ end
61
+ end
62
+ end
@@ -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.4"
4
4
  end
5
5
  end
@@ -1,6 +1,5 @@
1
1
  require 'middleman-core'
2
2
  require 'middleman/s3_sync'
3
- require 'map'
4
3
 
5
4
  module Middleman
6
5
  class S3SyncExtension < ::Middleman::Extension
@@ -77,7 +76,28 @@ module Middleman
77
76
  end
78
77
 
79
78
  def s3_sync_options
80
- options
79
+ self
80
+ end
81
+
82
+ def acl_enabled?
83
+ # ACLs are disabled if acl is explicitly set to nil, empty string, or false
84
+ acl_value = options.acl
85
+ return false if acl_value.nil? || acl_value == '' || acl_value == false
86
+ # Otherwise ACLs are enabled (using default or explicit value)
87
+ true
88
+ end
89
+
90
+ # Delegate option readers to the options object
91
+ def method_missing(method, *args, &block)
92
+ if options.respond_to?(method)
93
+ options.send(method, *args, &block)
94
+ else
95
+ super
96
+ end
97
+ end
98
+
99
+ def respond_to_missing?(method, include_private = false)
100
+ options.respond_to?(method) || super
81
101
  end
82
102
 
83
103
  # Read config options from an IO stream and set them on `self`. Defaults
@@ -20,15 +20,12 @@ 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'
27
25
  gem.add_runtime_dependency 'parallel'
28
26
  gem.add_runtime_dependency 'ruby-progressbar'
29
27
  gem.add_runtime_dependency 'ansi', '~> 1.5.0'
30
28
  gem.add_runtime_dependency 'mime-types', '~> 3.1'
31
- gem.add_runtime_dependency 'base64'
32
29
  gem.add_runtime_dependency 'nokogiri', '>= 1.18.4'
33
30
 
34
31
  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.4
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
@@ -80,20 +66,6 @@ dependencies:
80
66
  - - ">="
81
67
  - !ruby/object:Gem::Version
82
68
  version: '0'
83
- - !ruby/object:Gem::Dependency
84
- name: map
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '0'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '0'
97
69
  - !ruby/object:Gem::Dependency
98
70
  name: parallel
99
71
  requirement: !ruby/object:Gem::Requirement
@@ -150,20 +122,6 @@ dependencies:
150
122
  - - "~>"
151
123
  - !ruby/object:Gem::Version
152
124
  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
125
  - !ruby/object:Gem::Dependency
168
126
  name: nokogiri
169
127
  requirement: !ruby/object:Gem::Requirement
@@ -311,8 +269,9 @@ executables: []
311
269
  extensions: []
312
270
  extra_rdoc_files: []
313
271
  files:
314
- - ".envrc"
272
+ - ".envrc.backup"
315
273
  - ".gitignore"
274
+ - ".mise.toml"
316
275
  - ".rspec"
317
276
  - ".s3_sync.sample"
318
277
  - ".travis.yml"
@@ -329,6 +288,7 @@ files:
329
288
  - lib/middleman/s3_sync.rb
330
289
  - lib/middleman/s3_sync/caching_policy.rb
331
290
  - lib/middleman/s3_sync/cloudfront.rb
291
+ - lib/middleman/s3_sync/indifferent_hash.rb
332
292
  - lib/middleman/s3_sync/options.rb
333
293
  - lib/middleman/s3_sync/resource.rb
334
294
  - lib/middleman/s3_sync/status.rb
@@ -338,6 +298,8 @@ files:
338
298
  - spec/aws_sdk_parameters_spec.rb
339
299
  - spec/caching_policy_spec.rb
340
300
  - spec/cloudfront_spec.rb
301
+ - spec/extension_spec.rb
302
+ - spec/options_spec.rb
341
303
  - spec/resource_spec.rb
342
304
  - spec/s3_sync_integration_spec.rb
343
305
  - spec/spec_helper.rb
@@ -366,6 +328,8 @@ test_files:
366
328
  - spec/aws_sdk_parameters_spec.rb
367
329
  - spec/caching_policy_spec.rb
368
330
  - spec/cloudfront_spec.rb
331
+ - spec/extension_spec.rb
332
+ - spec/options_spec.rb
369
333
  - spec/resource_spec.rb
370
334
  - spec/s3_sync_integration_spec.rb
371
335
  - spec/spec_helper.rb
File without changes