middleman-cdn 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c7df50ddbc16cbcc2aad12f23bc81f594664f71c
4
- data.tar.gz: 2bbb29162f24ed5fb9470847460b3052612ed87b
3
+ metadata.gz: c0db2d194f1dd93aea915bd2a954c977f24678d4
4
+ data.tar.gz: c59dc210e2d7a273e5255a85376d283496b0d6d5
5
5
  SHA512:
6
- metadata.gz: 6413c23a7d9080e4846e2c73db67736aa03292567b2e85e6d3ee35ba8468f5a0fe27b11ea2bd4d152136a926317019176da5211e402a961dea6a9529b19495c7
7
- data.tar.gz: 70f29c73716425f684dabf4887fcb262362912de4f5e22b0751412a0fba1a7363297ca845960dbb20092367549291826b72f96f6a6199079cb35a08193a538c9
6
+ metadata.gz: a4f961344e60c4297417cc6152b0673387d44c786f8b505c5b0a4c68d44523a2dcb0fa2ffc184e2c29574c14c3d7bf2189d1abd5048512396edddacf3cb049c1
7
+ data.tar.gz: e586736630bf84c1d6eccd1eb997671f6cfc162f2f26f034952346cf1bb66e0b6ddae7a962461b799430d3af174cbabed46f18594b5894550d6d9b9d55bfc96d
data/README.md CHANGED
@@ -1,19 +1,22 @@
1
1
  # Middleman CDN [![Build Status](https://travis-ci.org/leighmcculloch/middleman-cdn.svg)](https://travis-ci.org/leighmcculloch/middleman-cdn) [![Dependency Status](https://gemnasium.com/leighmcculloch/middleman-cdn.png)](https://gemnasium.com/leighmcculloch/middleman-cdn)
2
+
3
+ ** Build status failing due to [RubyGem's dependency issues affecting some gems](https://twitter.com/rubygems_status/statuses/489119131862462464). Tests do pass.
4
+
2
5
  A [middleman](http://middlemanapp.com/) deploy tool for invalidating resources cached
3
6
  on common Content Delivery Networks (CDNs).
4
7
 
5
8
  * Cache invalidation of files on:
6
- * CloudFlare
7
- * Amazon CloudFront
9
+ * [CloudFlare](https://cloudflare.com)
10
+ * [Fastly](https://fastly.com)
11
+ * [Amazon CloudFront](http://aws.amazon.com/cloudfront/)
8
12
  * Select files for invalidation with regex.
9
13
  * Automatically invalidate after build.
10
14
  * Manually trigger invalidation with single command.
11
15
 
12
16
  What's next?
13
17
 
14
- * Expand the test base.
15
- * Add support for Fastly.
16
18
  * Add support for MaxCDN.
19
+ * Add support for RackspaceCDN (Akamai).
17
20
  * [Open an issue](../../issues/new) if you'd like your CDN provider added.
18
21
 
19
22
  # Usage
@@ -41,11 +44,16 @@ activate :cdn do |cdn|
41
44
  zone: 'example.com',
42
45
  base_urls: [
43
46
  'http://example.com',
44
- 'http://www.example.com',
45
47
  'https://example.com',
46
- 'https://www.example.com'
47
48
  ]
48
49
  }
50
+ cdn.fastly = {
51
+ api_key: '...', # default ENV['FASTLY_API_KEY']
52
+ base_urls: [
53
+ 'http://www.example.com',
54
+ 'https://www.example.com'
55
+ ],
56
+ }
49
57
  cdn.cloudfront = {
50
58
  access_key_id: '...', # default ENV['AWS_ACCESS_KEY_ID']
51
59
  secret_access_key: '...', # default ENV['AWS_SECRET_ACCESS_KEY']
@@ -83,7 +91,7 @@ The `cloudflare` parameter contains the information specific to your CloudFlare
83
91
  account and which zone (domain) files should be invalidated for. CloudFlare
84
92
  invalidation works off URLs not filenames, and you must provide a list of
85
93
  base urls to ensure we invalidate every URL that your files might be accessed
86
- from.
94
+ at.
87
95
 
88
96
  | Parameter | Description |
89
97
  |:--------- |:----------- |
@@ -92,7 +100,21 @@ from.
92
100
  | `zone` | The domain name of the website we are invalidating. |
93
101
  | `base_urls` | An array of base URLs that the files are accessible at. |
94
102
 
95
- CloudFlare invalidations take mere seconds.
103
+ CloudFlare invalidations often take a few seconds.
104
+
105
+ ### Configuration: Fastly
106
+
107
+ The `fastly` parameter contains the information specific to your Fastly
108
+ account. Fastly invalidation works off URLs not filenames, and you must provide
109
+ a list of base urls to ensure we invalidate every URL that your files might be
110
+ at.
111
+
112
+ | Parameter | Description |
113
+ |:--------- |:----------- |
114
+ | `api_key` | You can find this by logging into Fastly, going to your account page and it will be on the left. |
115
+ | `base_urls` | An array of base URLs that the files are accessible at. |
116
+
117
+ Fastly invalidations often take a few seconds.
96
118
 
97
119
  ### Configuration: CloudFront
98
120
 
@@ -115,7 +137,7 @@ on github, store them in environment variables, or execute on the
115
137
  commandline as:
116
138
 
117
139
  ```bash
118
- CLOUDFLARE_CLIENT_API_KEY= CLOUDFLARE_EMAIL= AWS_ACCESS_KEY= AWS_SECRET= bundle exec middleman invalidate
140
+ CLOUDFLARE_CLIENT_API_KEY= CLOUDFLARE_EMAIL= FASTLY_API_KEY= AWS_ACCESS_KEY= AWS_SECRET= bundle exec middleman invalidate
119
141
  ```
120
142
 
121
143
  ## Invalidating
@@ -148,6 +170,6 @@ Thanks to @b4k3r for the [Cloudflare gem](https://github.com/b4k3r/cloudflare) t
148
170
 
149
171
  ## Why Middleman CDN
150
172
 
151
- Middleman CloudFront is a great extension for Middleman and perfect if you're using CloudFront. I use CloudFlare primarily and I wanted to create a similar extension for it specifically. However, it's becoming increasingly common for static websites to be hosted across multiple CDNs. [jsDelivr](http://jsdelivr.com/) is a well known promoter of this strategy.
173
+ Middleman CloudFront is a great extension for Middleman and perfect if you're using CloudFront. I needed a similar extension for CloudFlare, however it's becoming increasingly common for static websites to be hosted across multiple CDNs. [jsDelivr](http://jsdelivr.com/) is a well known promoter of this strategy.
152
174
 
153
175
  In light of the new trends in how we are using CDNs, I decided it would be more worthwhile to create an extension that can grow to support all the popular CDNs.
@@ -4,7 +4,7 @@ module Middleman
4
4
  class BaseCDN
5
5
  def self.example_configuration
6
6
  config_lines = self.example_configuration_elements.map do |config_key, config_info|
7
- " #{config_key.to_s}: #{config_info[0]}".ljust(30) + " #{config_info[1]}"
7
+ " #{config_key.to_s}: #{config_info[0]},".ljust(30) + " #{config_info[1]}"
8
8
  end
9
9
 
10
10
  <<-TEXT
@@ -0,0 +1,57 @@
1
+ #Encoding: UTF-8
2
+ require "fastly"
3
+ require "active_support/core_ext/string"
4
+
5
+ module Middleman
6
+ module Cli
7
+
8
+ class FastlyCDN < BaseCDN
9
+ def self.key
10
+ "fastly"
11
+ end
12
+
13
+ def self.example_configuration_elements
14
+ {
15
+ api_key: ['"..."', "# default ENV['FASTLY_API_KEY']"],
16
+ base_urls: [['http://www.example.com', 'https://www.example.com'], ""]
17
+ }
18
+ end
19
+
20
+ def invalidate(options, files)
21
+ options[:api_key] ||= ENV['FASTLY_API_KEY']
22
+
23
+ [:api_key, :base_urls].each do |key|
24
+ if options[key].blank?
25
+ say_status("Error: Configuration key fastly[:#{key}] is missing.".light_red)
26
+ raise
27
+ end
28
+ end
29
+
30
+ options[:base_urls] = [options[:base_urls]] if options[:base_urls].is_a?(String)
31
+ if !options[:base_urls].is_a?(Array)
32
+ say_status("Error: Configuration key fastly[:base_urls] must be an array and contain at least one base url.".light_red)
33
+ raise
34
+ end
35
+
36
+ fastly = ::Fastly.new({
37
+ :api_key => options[:api_key]
38
+ })
39
+
40
+ options[:base_urls].each do |base_url|
41
+ files.each do |file|
42
+ begin
43
+ url = "#{base_url}#{file}"
44
+ say_status("Invalidating #{url}... ", newline: false)
45
+ fastly.purge("#{base_url}#{file}")
46
+ rescue => e
47
+ say_status(", " + "error: #{e.message}".light_red, header: false)
48
+ else
49
+ say_status("✔".light_green, header: false)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -3,6 +3,7 @@ require "middleman-cdn/extension"
3
3
  require "middleman-cdn/cdns/base.rb"
4
4
  require "middleman-cdn/cdns/cloudflare.rb"
5
5
  require "middleman-cdn/cdns/cloudfront.rb"
6
+ require "middleman-cdn/cdns/fastly.rb"
6
7
  require "colorize"
7
8
 
8
9
  module Middleman
@@ -63,7 +64,8 @@ module Middleman
63
64
  def cdns
64
65
  [
65
66
  CloudFlareCDN,
66
- CloudFrontCDN
67
+ CloudFrontCDN,
68
+ FastlyCDN
67
69
  ]
68
70
  end
69
71
 
@@ -11,6 +11,7 @@ module Middleman
11
11
  class CDNExtension < Middleman::Extension
12
12
  option :cloudflare, nil, 'CloudFlare options'
13
13
  option :cloudfront, nil, 'CloudFront options'
14
+ option :fastly, nil, 'Fastly options'
14
15
  option :filter, nil, 'Cloudflare options'
15
16
  option :after_build, false, 'Cloudflare options'
16
17
 
@@ -1,5 +1,5 @@
1
1
  module Middleman
2
2
  module CDN
3
- VERSION = '0.1.7'
3
+ VERSION = '0.1.8'
4
4
  end
5
5
  end
@@ -19,6 +19,7 @@ Gem::Specification.new do |s|
19
19
 
20
20
  s.add_dependency 'fog', '~> 1.9'
21
21
  s.add_dependency 'cloudflare', '~> 2.0'
22
+ s.add_dependency 'fastly', '~> 1.1'
22
23
  s.add_dependency 'colorize', '~> 0.7'
23
24
  s.add_dependency 'activesupport', '~> 4.1'
24
25
 
@@ -30,8 +30,8 @@ shared_examples "BaseCDN" do
30
30
  expect(described_class.example_configuration).to eq(
31
31
  <<-TEXT
32
32
  cdn.#{described_class.key} = {
33
- key1: "value" # comment
34
- key2: ["arr1", "arr2"] #
33
+ key1: "value", # comment
34
+ key2: ["arr1", "arr2"], #
35
35
  }
36
36
  TEXT
37
37
  )
@@ -0,0 +1,196 @@
1
+ #Encoding: UTF-8
2
+ require 'spec_helper'
3
+ require 'lib/middleman-cdn/cdns/base_protocol'
4
+
5
+ describe Middleman::Cli::FastlyCDN do
6
+ it_behaves_like "BaseCDN"
7
+
8
+ describe '.key' do
9
+ it "should be 'fastly'" do
10
+ expect(described_class.key).to eq("fastly")
11
+ end
12
+ end
13
+
14
+ describe '.example_configuration_elements' do
15
+ it "should contain these keys" do
16
+ required_keys = [:api_key, :base_urls]
17
+ expect(described_class.example_configuration_elements.keys).to eq(required_keys)
18
+ end
19
+ end
20
+
21
+ describe '#invalidate' do
22
+ let(:double_fastly) { double("::Fastly") }
23
+
24
+ before do
25
+ allow(double_fastly).to receive(:purge)
26
+ allow(::Fastly).to receive(:new).and_return(double_fastly)
27
+ end
28
+
29
+ let(:files) { [ "/index.html", "/", "/test/index.html", "/test/image.png" ] }
30
+
31
+ context "all options provided" do
32
+ let(:options) do
33
+ {
34
+ api_key: '000000000000000000000',
35
+ base_urls: ['http://www.example.com', 'https://www.example.com']
36
+ }
37
+ end
38
+
39
+ it "should connect to fastly with credentails" do
40
+ expect(::Fastly).to receive(:new).with({:api_key => "000000000000000000000"})
41
+ subject.invalidate(options, files)
42
+ end
43
+
44
+ it "should not raise errors" do
45
+ subject.invalidate(options, files)
46
+ end
47
+
48
+ it "should call cloudflare to purge each file for each base url" do
49
+ expect(double_fastly).to receive(:purge).once.ordered.with("http://www.example.com/index.html")
50
+ expect(double_fastly).to receive(:purge).once.ordered.with("http://www.example.com/")
51
+ expect(double_fastly).to receive(:purge).once.ordered.with("http://www.example.com/test/index.html")
52
+ expect(double_fastly).to receive(:purge).once.ordered.with("http://www.example.com/test/image.png")
53
+ expect(double_fastly).to receive(:purge).once.ordered.with("https://www.example.com/index.html")
54
+ expect(double_fastly).to receive(:purge).once.ordered.with("https://www.example.com/")
55
+ expect(double_fastly).to receive(:purge).once.ordered.with("https://www.example.com/test/index.html")
56
+ expect(double_fastly).to receive(:purge).once.ordered.with("https://www.example.com/test/image.png")
57
+ subject.invalidate(options, files)
58
+ end
59
+
60
+ it "should output saying invalidating each file checkmarks" do
61
+ files_escaped = files.map { |file| Regexp.escape(file) }
62
+ expect { subject.invalidate(options, files) }.to output(/#{files_escaped.join(".+")}/m).to_stdout
63
+ end
64
+
65
+ it "should output indicating invalidating on all base urls" do
66
+ base_urls_escaped = options[:base_urls].map { |base_url| Regexp.escape(base_url) }
67
+ expect { subject.invalidate(options, files) }.to output(/#{base_urls_escaped.join(".+")}/m).to_stdout
68
+ end
69
+
70
+ it "should output saying success checkmarks" do
71
+ expect { subject.invalidate(options, files) }.to output(/✔/).to_stdout
72
+ end
73
+
74
+ context "and errors occurs when purging" do
75
+ before do
76
+ allow(double_fastly).to receive(:purge).and_raise(StandardError)
77
+ end
78
+
79
+ it "should output saying error information" do
80
+ expect { subject.invalidate(options, files) }.to output(/error: StandardError/).to_stdout
81
+ end
82
+ end
83
+ end
84
+
85
+ context "environment variables used for credentials" do
86
+ before do
87
+ allow(ENV).to receive(:[])
88
+ allow(ENV).to receive(:[]).with("FASTLY_API_KEY").and_return("111111111111111111111")
89
+ end
90
+
91
+ let(:options) do
92
+ {
93
+ base_urls: ['http://www.example.com', 'https://www.example.com']
94
+ }
95
+ end
96
+
97
+ it "should connect to fastly with environment variable credentails" do
98
+ expect(::Fastly).to receive(:new).with({:api_key => "111111111111111111111"})
99
+ subject.invalidate(options, files)
100
+ end
101
+
102
+ it "should not raise errors" do
103
+ subject.invalidate(options, files)
104
+ end
105
+ end
106
+
107
+ context "if client_api_key not provided" do
108
+ before do
109
+ allow(ENV).to receive(:[])
110
+ allow(ENV).to receive(:[]).with("FASTLY_API_KEY").and_return(nil)
111
+ end
112
+
113
+ let(:options) do
114
+ {
115
+ base_urls: ['http://www.example.com', 'https://www.example.com']
116
+ }
117
+ end
118
+
119
+ it "should raise error" do
120
+ expect { subject.invalidate(options, files) }.to raise_error(RuntimeError)
121
+ end
122
+
123
+ it "should output saying error" do
124
+ expect { subject.invalidate(options, files) rescue nil }.to output(/Error: Configuration key fastly\[:api_key\] is missing\./).to_stdout
125
+ end
126
+ end
127
+
128
+ context "if base_urls not provided" do
129
+ let(:options) do
130
+ {
131
+ api_key: '000000000000000000000'
132
+ }
133
+ end
134
+
135
+ it "should raise error" do
136
+ expect { subject.invalidate(options, files) }.to raise_error(RuntimeError)
137
+ end
138
+
139
+ it "should output saying error" do
140
+ expect { subject.invalidate(options, files) rescue nil }.to output(/Error: Configuration key fastly\[:base_urls\] is missing\./).to_stdout
141
+ end
142
+ end
143
+
144
+ context "if base_urls is a String" do
145
+ let(:options) do
146
+ {
147
+ api_key: '000000000000000000000',
148
+ base_urls: 'http://sub.example.com'
149
+ }
150
+ end
151
+
152
+ it "should not raise error" do
153
+ expect { subject.invalidate(options, files) }.to_not raise_error
154
+ end
155
+
156
+ it "should output indicating invalidating on the one base url" do
157
+ base_url_escaped = Regexp.escape(options[:base_urls])
158
+ expect { subject.invalidate(options, files) }.to output(/#{base_url_escaped}/).to_stdout
159
+ end
160
+ end
161
+
162
+ context "if base_urls is an empty Array" do
163
+ let(:options) do
164
+ {
165
+ api_key: '000000000000000000000',
166
+ base_urls: []
167
+ }
168
+ end
169
+
170
+ it "should raise error" do
171
+ expect { subject.invalidate(options, files) }.to raise_error(RuntimeError)
172
+ end
173
+
174
+ it "should output saying error" do
175
+ expect { subject.invalidate(options, files) rescue nil }.to output(/Error: Configuration key fastly\[:base_urls\] is missing\./).to_stdout
176
+ end
177
+ end
178
+
179
+ context "if base_urls is not an Array or String" do
180
+ let(:options) do
181
+ {
182
+ api_key: '000000000000000000000',
183
+ base_urls: 200
184
+ }
185
+ end
186
+
187
+ it "should raise error" do
188
+ expect { subject.invalidate(options, files) }.to raise_error(RuntimeError)
189
+ end
190
+
191
+ it "should output saying error" do
192
+ expect { subject.invalidate(options, files) rescue nil }.to output(/Error: Configuration key fastly\[:base_urls\] must be an array and contain at least one base url\./).to_stdout
193
+ end
194
+ end
195
+ end
196
+ end
@@ -37,6 +37,7 @@ describe Middleman::Cli::CDN do
37
37
  it "should invalidate the files with only cloudflare" do
38
38
  expect_any_instance_of(::Middleman::Cli::CloudFlareCDN).to_not receive(:invalidate)
39
39
  expect_any_instance_of(::Middleman::Cli::CloudFrontCDN).to_not receive(:invalidate)
40
+ expect_any_instance_of(::Middleman::Cli::FastlyCDN).to_not receive(:invalidate)
40
41
  expect { subject.cdn_invalidate(options) }.to raise_error(RuntimeError)
41
42
  end
42
43
  end
@@ -52,6 +53,7 @@ describe Middleman::Cli::CDN do
52
53
  it "should invalidate the files with only cloudflare" do
53
54
  expect_any_instance_of(::Middleman::Cli::CloudFlareCDN).to receive(:invalidate).with(options.cloudflare, ["/index.html", "/image.png", "/"])
54
55
  expect_any_instance_of(::Middleman::Cli::CloudFrontCDN).to_not receive(:invalidate)
56
+ expect_any_instance_of(::Middleman::Cli::FastlyCDN).to_not receive(:invalidate)
55
57
  subject.cdn_invalidate(options)
56
58
  end
57
59
  end
@@ -61,6 +63,7 @@ describe Middleman::Cli::CDN do
61
63
  OpenStruct.new({
62
64
  cloudflare: {},
63
65
  cloudfront: {},
66
+ fastly: {},
64
67
  filter: /.*/
65
68
  })
66
69
  end
@@ -68,6 +71,7 @@ describe Middleman::Cli::CDN do
68
71
  it "should invalidate the files with all cdns" do
69
72
  expect_any_instance_of(::Middleman::Cli::CloudFlareCDN).to receive(:invalidate).with(options.cloudflare, ["/index.html", "/image.png", "/"])
70
73
  expect_any_instance_of(::Middleman::Cli::CloudFrontCDN).to receive(:invalidate).with(options.cloudfront, ["/index.html", "/image.png", "/"])
74
+ expect_any_instance_of(::Middleman::Cli::FastlyCDN).to receive(:invalidate).with(options.fastly, ["/index.html", "/image.png", "/"])
71
75
  subject.cdn_invalidate(options)
72
76
  end
73
77
  end
@@ -78,6 +82,7 @@ describe Middleman::Cli::CDN do
78
82
  OpenStruct.new({
79
83
  cloudflare: {},
80
84
  cloudfront: {},
85
+ fastly: {},
81
86
  filter: /\.html/
82
87
  })
83
88
  end
@@ -85,6 +90,7 @@ describe Middleman::Cli::CDN do
85
90
  it "should invalidate the files with all cdns" do
86
91
  expect_any_instance_of(::Middleman::Cli::CloudFlareCDN).to receive(:invalidate).with(options.cloudflare, ["/index.html", "/"])
87
92
  expect_any_instance_of(::Middleman::Cli::CloudFrontCDN).to receive(:invalidate).with(options.cloudfront, ["/index.html", "/"])
93
+ expect_any_instance_of(::Middleman::Cli::FastlyCDN).to receive(:invalidate).with(options.cloudfront, ["/index.html", "/"])
88
94
  subject.cdn_invalidate(options)
89
95
  end
90
96
  end
@@ -94,6 +100,7 @@ describe Middleman::Cli::CDN do
94
100
  OpenStruct.new({
95
101
  cloudflare: {},
96
102
  cloudfront: {},
103
+ fastly: {},
97
104
  filter: /\.htm$/
98
105
  })
99
106
  end
@@ -101,6 +108,7 @@ describe Middleman::Cli::CDN do
101
108
  it "should invalidate the files with all cdns" do
102
109
  expect_any_instance_of(::Middleman::Cli::CloudFlareCDN).to_not receive(:invalidate)
103
110
  expect_any_instance_of(::Middleman::Cli::CloudFrontCDN).to_not receive(:invalidate)
111
+ expect_any_instance_of(::Middleman::Cli::FastlyCDN).to_not receive(:invalidate)
104
112
  subject.cdn_invalidate(options)
105
113
  end
106
114
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: middleman-cdn
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Leigh McCulloch
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fastly
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.1'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.1'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: colorize
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -125,6 +139,7 @@ files:
125
139
  - lib/middleman-cdn/cdns/base.rb
126
140
  - lib/middleman-cdn/cdns/cloudflare.rb
127
141
  - lib/middleman-cdn/cdns/cloudfront.rb
142
+ - lib/middleman-cdn/cdns/fastly.rb
128
143
  - lib/middleman-cdn/commands.rb
129
144
  - lib/middleman-cdn/extension.rb
130
145
  - lib/middleman-cdn/version.rb
@@ -133,6 +148,7 @@ files:
133
148
  - spec/lib/middleman-cdn/cdns/base_protocol.rb
134
149
  - spec/lib/middleman-cdn/cdns/cloudflare_spec.rb
135
150
  - spec/lib/middleman-cdn/cdns/cloudfront_spec.rb
151
+ - spec/lib/middleman-cdn/cdns/fastly_spec.rb
136
152
  - spec/lib/middleman-cdn/commands_spec.rb
137
153
  - spec/spec_helper.rb
138
154
  homepage: https://github.com/leighmcculloch/middleman-cdn