middleman-s3_sync 4.7.0 → 4.8.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b94ebf16b78ae47530a08332b802f50aa9c4510a47b07b60cab9a8b004576e0
4
- data.tar.gz: 6d097be5659e364f9c789164da5838bed6781fbb0db3a4724433d2b78d43cbad
3
+ metadata.gz: 1ca1bccd213b5e15c13b3c24763c20164493043d5ed3772b90ac38dc8391293d
4
+ data.tar.gz: 5f2b316721b80826dfe8b33f96ba3b5389b455260a256ff8cf7562cd563a1d28
5
5
  SHA512:
6
- metadata.gz: 0f29594758ad18658482ee676c9fa2d505dbe133ccd164e9a76816a7803982736f644f62193b61a45aa5bc4204439dad2c0911852dad6b596897caed93fbfd4a
7
- data.tar.gz: 5f309d8e5aa6b43e0b6cb76cf7be241b4d198cff4b87d561f33c140c2886cd9337be6f7187b70f38235bed8e469deeb9ce02fb7433c82c8ca08f208cb9a28a02
6
+ metadata.gz: 3ce61465bdc1d5986030e772d632fc425bc7d97b3d2d948ae5afee9bda9f5b92edb44cfd7bef8ad9d3f435a5c111945091f1e62428d3fd20d803a06a5e4f8e7d
7
+ data.tar.gz: a97276e8b0246ca38c8e5f95a0628b18d980185b366b558979dddeeb414ef2538201f3ce8697d9294390154d096676a71d2545b3fb1f84836d86d72feb53483f
data/Changelog.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  The gem that tries really hard not to push files to S3.
4
4
 
5
+ ## v4.8.0
6
+ - Add `immutable` directive to caching policies. Pair with a long `max_age` on
7
+ fingerprinted assets (e.g. those produced by Middleman's `asset_hash`
8
+ extension) to tell browsers they never need to revalidate:
9
+ `caching_policy 'text/css', max_age: 1.year, public: true, immutable: true`.
10
+ - Prefer `Cache-Control: max-age` over the `Expires` header. When a policy sets
11
+ `max_age:`, the `Expires` header is now suppressed even if `expires:` is also
12
+ set. Per RFC 7234 §5.3, `max-age` overrides `Expires` for HTTP/1.1 caches, so
13
+ emitting both adds no information and forces a metadata update on every build
14
+ as the `expires:` timestamp drifts forward. Existing configs that rely solely
15
+ on `expires:` (without `max_age:`) continue to work unchanged.
16
+ - Resource uploads no longer include empty `cache_control` or `expires` keys
17
+ when the caching policy yields `nil` for them.
18
+ - Drop `timerizer` development dependency. Its monkey-patch of `Time.new` was
19
+ incompatible with Ruby 3.1.7's keyword-argument changes and crashed RSpec at
20
+ startup on the 3.1 CI matrix. The three test usages were replaced with plain
21
+ `Time` literals.
22
+
23
+ ## v4.7.0
24
+ - Add `after_s3_sync` callback hook that runs after sync completes (#138).
25
+ Accepts a lambda/proc — optionally receiving a results hash with `created`,
26
+ `updated`, `deleted` counts and invalidation paths — or a symbol referencing
27
+ a method on the Middleman app. Zero-arg callbacks work without modification
28
+ via arity detection. Callback failures are logged but do not abort the sync.
29
+ - Add `scan_build_dir` option (default: `false`) to sync files in the build
30
+ directory that aren't in the Middleman sitemap (#108, #137). Useful for
31
+ output from `after_build` callbacks, image optimizers, or anything placed
32
+ in `build/` outside the sitemap.
33
+ - Add `routing_rules` option to configure S3 website routing rules at sync
34
+ time, so deployments don't overwrite manually-configured rules (#142).
35
+ Supports `condition.key_prefix_equals`,
36
+ `condition.http_error_code_returned_equals`, and the
37
+ `redirect.{host_name, http_redirect_code, protocol, replace_key_prefix_with,
38
+ replace_key_with}` keys. Requires `index_document` to also be set.
39
+ - Improve content type detection with a `mime-types` fallback for files
40
+ Middleman doesn't classify (e.g. orphan files, WebP, woff2). The
41
+ `content_types` option is now checked first, and unknown extensions default
42
+ to `application/octet-stream`. Bumped `mime-types` constraint to `~> 3.4`.
43
+ (#161)
44
+ - Fix sitemap population for build-mode extensions (#116, #128). The sync
45
+ now calls `ensure_resource_list_updated!` so extensions like blog and
46
+ asset_hash populate the sitemap, and always runs in `:build` mode so
47
+ `configure :build` blocks are active. Files emitted by `after_build`
48
+ callbacks still aren't visible to the sitemap — use `scan_build_dir` for
49
+ those.
50
+ - Fix `Resource#redirect?` to return `true`/`false` instead of a truthy URL
51
+ string (#143). The status logic was already correct; this just normalizes
52
+ the boolean contract.
53
+ - Tighten gemspec dependency bounds and require Ruby `>= 3.0` (#167).
54
+ Pessimistic constraints on all runtime and development deps to resolve the
55
+ open-ended-dependency warnings on `gem build`.
56
+ - Add GitHub Actions CI matrix (Ruby 3.1, 3.2, 3.3, 3.4) and an automated
57
+ RubyGems release workflow that publishes on `v*` tags (#169).
58
+
5
59
  ## v4.6.5
6
60
  - Performance and stability improvements
7
61
  - Thread-safe invalidation path tracking (use Set + mutex) when running in parallel
data/README.md CHANGED
@@ -546,6 +546,21 @@ The following keys can be set:
546
546
  | `no_store` | boolean | `no-store` | Instructs caches not to keep a copy of the representation under any conditions. |
547
547
  | `must_revalidate` | boolean | `must-revalidate` | Tells the caches that they must obey any freshness information you give them about a representation. |
548
548
  | `proxy_revalidate` | boolean | `proxy-revalidate` | Similar as `must-revalidate`, but only for proxies. |
549
+ | `immutable` | boolean | `immutable` | Tells browsers the response body will never change for this URL, so they should not revalidate even on a user-initiated reload. Pair with a long `max_age` for content-addressed (fingerprinted) assets. |
550
+
551
+ #### Hashed Assets
552
+
553
+ If you use Middleman's `asset_hash` extension, fingerprinted asset URLs
554
+ are content-addressed and never need to be revalidated. Combining a
555
+ long `max_age` with `immutable` is the strongest browser cache hint
556
+ you can give:
557
+
558
+ ```ruby
559
+ caching_policy 'text/css', max_age: 1.year, public: true, immutable: true
560
+ caching_policy 'application/javascript', max_age: 1.year, public: true, immutable: true
561
+ caching_policy 'image/png', max_age: 1.year, public: true, immutable: true
562
+ caching_policy 'image/jpeg', max_age: 1.year, public: true, immutable: true
563
+ ```
549
564
 
550
565
  #### Setting `Expires` Header
551
566
 
@@ -554,8 +569,11 @@ You can pass the `expires` key to the `caching_policy` and
554
569
  header on a results. You will need to pass it a Time object indicating
555
570
  when the resource is set to expire.
556
571
 
557
- > Note that the `Cache-Control` header will take precedence over the
558
- > `Expires` header if both are present.
572
+ > Note that the `Cache-Control` header takes precedence over `Expires`
573
+ > for HTTP/1.1 caches (RFC 7234 §5.3). For that reason, when a policy
574
+ > sets `max_age:`, the `Expires` header is suppressed entirely — even
575
+ > if `expires:` is also set. This avoids redundant metadata churn from
576
+ > the `expires:` timestamp drifting forward on every build.
559
577
 
560
578
  #### A Note About Browser Caching
561
579
 
@@ -38,6 +38,7 @@ module Middleman
38
38
  policy << "no-store" if policies.fetch(:no_store, false)
39
39
  policy << "must-revalidate" if policies.fetch(:must_revalidate, false)
40
40
  policy << "proxy-revalidate" if policies.fetch(:proxy_revalidate, false)
41
+ policy << "immutable" if policies.fetch(:immutable, false)
41
42
  if policy.empty?
42
43
  nil
43
44
  else
@@ -49,7 +50,12 @@ module Middleman
49
50
  cache_control
50
51
  end
51
52
 
53
+ # Returns an RFC 1123 date for the Expires header.
54
+ # When :max_age is set we suppress Expires entirely: per RFC 7234 §5.3
55
+ # max-age takes precedence over Expires for HTTP/1.1 caches, so emitting
56
+ # both adds no information and forces a metadata update on every build.
52
57
  def expires
58
+ return nil if policies.has_key?(:max_age)
53
59
  if expiration = policies.fetch(:expires, nil)
54
60
  CGI.rfc1123_date(expiration)
55
61
  end
@@ -64,8 +64,8 @@ module Middleman
64
64
  attributes[:acl] = options.acl if options.acl_enabled?
65
65
 
66
66
  if caching_policy
67
- attributes[:cache_control] = caching_policy.cache_control
68
- attributes[:expires] = caching_policy.expires
67
+ attributes[:cache_control] = caching_policy.cache_control if caching_policy.cache_control
68
+ attributes[:expires] = caching_policy.expires if caching_policy.expires
69
69
  end
70
70
 
71
71
  if options.prefer_gzip && gzipped
@@ -382,8 +382,8 @@ module Middleman
382
382
 
383
383
  # Add cache control and expires if present
384
384
  if caching_policy
385
- upload_options[:cache_control] = caching_policy.cache_control
386
- upload_options[:expires] = caching_policy.expires
385
+ upload_options[:cache_control] = caching_policy.cache_control if caching_policy.cache_control
386
+ upload_options[:expires] = caching_policy.expires if caching_policy.expires
387
387
  end
388
388
 
389
389
  # Add storage class if needed
@@ -1,5 +1,5 @@
1
1
  module Middleman
2
2
  module S3Sync
3
- VERSION = "4.7.0"
3
+ VERSION = "4.8.0"
4
4
  end
5
5
  end
@@ -39,6 +39,5 @@ Gem::Specification.new do |gem|
39
39
  gem.add_development_dependency 'rspec-support', '~> 3.12'
40
40
  gem.add_development_dependency 'rspec-its', '~> 2.0'
41
41
  gem.add_development_dependency 'rspec-mocks', '~> 3.12'
42
- gem.add_development_dependency 'timerizer', '~> 0.3'
43
42
  gem.add_development_dependency 'webrick', '~> 1.8'
44
43
  end
@@ -479,6 +479,48 @@ describe 'AWS SDK Parameter Validation' do
479
479
  end
480
480
  end
481
481
 
482
+ context 'when a caching policy with only max_age is in effect' do
483
+ before do
484
+ policy = Middleman::S3Sync::BrowserCachePolicy.new(max_age: 31536000, public: true, immutable: true)
485
+ allow(Middleman::S3Sync).to receive(:caching_policy_for).and_return(policy)
486
+ end
487
+
488
+ it 'sets cache_control but omits expires' do
489
+ attributes = resource.to_h
490
+
491
+ expect(attributes[:cache_control]).to eq('max-age=31536000, public, immutable')
492
+ expect(attributes).not_to have_key(:expires)
493
+ end
494
+ end
495
+
496
+ context 'when a caching policy with only expires is in effect' do
497
+ before do
498
+ policy = Middleman::S3Sync::BrowserCachePolicy.new(expires: Time.utc(2030, 1, 1))
499
+ allow(Middleman::S3Sync).to receive(:caching_policy_for).and_return(policy)
500
+ end
501
+
502
+ it 'sets expires but omits cache_control' do
503
+ attributes = resource.to_h
504
+
505
+ expect(attributes[:expires]).to eq(CGI.rfc1123_date(Time.utc(2030, 1, 1)))
506
+ expect(attributes).not_to have_key(:cache_control)
507
+ end
508
+ end
509
+
510
+ context 'when a caching policy sets both max_age and expires' do
511
+ before do
512
+ policy = Middleman::S3Sync::BrowserCachePolicy.new(max_age: 300, expires: Time.utc(2030, 1, 1))
513
+ allow(Middleman::S3Sync).to receive(:caching_policy_for).and_return(policy)
514
+ end
515
+
516
+ it 'emits cache_control and suppresses the redundant expires' do
517
+ attributes = resource.to_h
518
+
519
+ expect(attributes[:cache_control]).to eq('max-age=300')
520
+ expect(attributes).not_to have_key(:expires)
521
+ end
522
+ end
523
+
482
524
  context 'when resource has a redirect' do
483
525
  let(:mm_resource) do
484
526
  double(
@@ -17,6 +17,7 @@ describe Middleman::S3Sync::BrowserCachePolicy do
17
17
  expect(policy.to_s).to_not match /no-store/
18
18
  expect(policy.to_s).to_not match /must-revalidate/
19
19
  expect(policy.to_s).to_not match /proxy-revalidate/
20
+ expect(policy.to_s).to_not match /immutable/
20
21
  expect(policy.expires).to eq nil
21
22
  end
22
23
 
@@ -62,6 +63,19 @@ describe Middleman::S3Sync::BrowserCachePolicy do
62
63
  its(:to_s) { is_expected.to match /proxy-revalidate/ }
63
64
  end
64
65
 
66
+ context "setting the immutable flag" do
67
+ let(:options) { { immutable: true } }
68
+ its(:to_s) { is_expected.to match /immutable/ }
69
+ end
70
+
71
+ context "combining max_age, public, and immutable for hashed assets" do
72
+ let(:options) { { max_age: 31536000, public: true, immutable: true } }
73
+
74
+ it "emits the directives in policy order" do
75
+ expect(policy.to_s).to eq "max-age=31536000, public, immutable"
76
+ end
77
+ end
78
+
65
79
  context "divide caching policiies with a comma and a space" do
66
80
  let(:options) { { :max_age => 300, :public => true } }
67
81
 
@@ -74,9 +88,20 @@ describe Middleman::S3Sync::BrowserCachePolicy do
74
88
  end
75
89
 
76
90
  context "set the expiration date" do
77
- let(:options) { { expires: 1.years.from_now } }
91
+ let(:expires_at) { Time.utc(2030, 1, 1) }
92
+ let(:options) { { expires: expires_at } }
93
+
94
+ its(:expires) { is_expected.to eq CGI.rfc1123_date(expires_at) }
95
+ end
96
+
97
+ context "max_age suppresses the Expires header" do
98
+ let(:options) { { max_age: 300, expires: Time.utc(2030, 1, 1) } }
99
+
100
+ it "still emits max-age in cache_control" do
101
+ expect(policy.to_s).to match /max-age=300/
102
+ end
78
103
 
79
- its(:expires) { is_expected.to eq CGI.rfc1123_date(1.year.from_now )}
104
+ its(:expires) { is_expected.to be_nil }
80
105
  end
81
106
  end
82
107
  end
data/spec/spec_helper.rb CHANGED
@@ -6,7 +6,6 @@
6
6
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
7
 
8
8
  require 'middleman-s3_sync'
9
- require 'timerizer'
10
9
  require 'rspec/its'
11
10
  require 'rspec/support'
12
11
  require 'digest/md5'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: middleman-s3_sync
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.7.0
4
+ version: 4.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Frederic Jean
@@ -246,20 +246,6 @@ dependencies:
246
246
  - - "~>"
247
247
  - !ruby/object:Gem::Version
248
248
  version: '3.12'
249
- - !ruby/object:Gem::Dependency
250
- name: timerizer
251
- requirement: !ruby/object:Gem::Requirement
252
- requirements:
253
- - - "~>"
254
- - !ruby/object:Gem::Version
255
- version: '0.3'
256
- type: :development
257
- prerelease: false
258
- version_requirements: !ruby/object:Gem::Requirement
259
- requirements:
260
- - - "~>"
261
- - !ruby/object:Gem::Version
262
- version: '0.3'
263
249
  - !ruby/object:Gem::Dependency
264
250
  name: webrick
265
251
  requirement: !ruby/object:Gem::Requirement