upstatic 0.1.0.pre
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 +7 -0
- data/.gitignore +18 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +106 -0
- data/Rakefile +9 -0
- data/bin/upstatic +3 -0
- data/lib/upstatic.rb +15 -0
- data/lib/upstatic/cli.rb +20 -0
- data/lib/upstatic/compat.rb +12 -0
- data/lib/upstatic/configuration.rb +45 -0
- data/lib/upstatic/deployer.rb +145 -0
- data/lib/upstatic/manager.rb +33 -0
- data/lib/upstatic/options.rb +44 -0
- data/lib/upstatic/quiet_shell.rb +7 -0
- data/lib/upstatic/shell.rb +13 -0
- data/lib/upstatic/tool.rb +40 -0
- data/lib/upstatic/verifier.rb +59 -0
- data/lib/upstatic/version.rb +3 -0
- data/test/cases/helper.rb +2 -0
- data/test/cases/test_cache_control.rb +39 -0
- data/test/cases/test_case.rb +76 -0
- data/test/cases/test_deployment.rb +61 -0
- data/test/cases/test_gzip.rb +56 -0
- data/test/cases/test_invalidations.rb +64 -0
- data/test/cases/test_multiple_deployments.rb +74 -0
- data/test/minitest_helper.rb +20 -0
- data/test/test_configuration.rb +33 -0
- data/test/upload_dir/Upstatic +1 -0
- data/test/upload_dir/folder/index.html +1 -0
- data/test/upload_dir/index.html +1 -0
- data/test/upload_dir/scripts.js +1 -0
- data/test/upload_dir/styles.css +1 -0
- data/upstatic.gemspec +26 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d8c0dafd826bfe88476003a538e4a1d224d3aa7
|
4
|
+
data.tar.gz: 1b1c06c646502e866dfc2a8efd64d30b273a187d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8f8082e3e79931cf3089570dea57ead37aa21d4507b4d95ed372c84a4d09861dc2634c8aacc2811d4111bc73f3e6fde66706069f986ef92ff32af657e200c8ca
|
7
|
+
data.tar.gz: 99d79cba6f0c0bf7a60acd594802d1bb38e5b0fd0934f833ec897f0787250ee2da19f04ca064bf94b55c33e05d5b0d0174179d229d9729067d3fd41eee418efa
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Joao Carlos
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Upstatic
|
2
|
+
|
3
|
+
Deploy your static sites to S3. Supports Cloudront object invalidation,
|
4
|
+
file gzipping, and configuration of cache control headers.
|
5
|
+
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Until upstatic is released on rubygems, please clone the repository and
|
10
|
+
install with:
|
11
|
+
|
12
|
+
rake install
|
13
|
+
|
14
|
+
## Usage
|
15
|
+
|
16
|
+
Create a file named _Upstatic_ in the directory you want to deploy and
|
17
|
+
specify at least the S3 bucket name:
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
bucket "upstatic-site"
|
21
|
+
```
|
22
|
+
|
23
|
+
Upstatic tries to read your AWS credentials from the following
|
24
|
+
environment variables: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`.
|
25
|
+
|
26
|
+
You can also specify them in the _Upstatic_ file as:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
access_key_id "MY_AWS_KEY_ID"
|
30
|
+
secret_access_key "MY_AWS_SECRET_ACCESS_KEY"
|
31
|
+
```
|
32
|
+
|
33
|
+
Once the bucket and credentials are setup, you can deploy by running:
|
34
|
+
|
35
|
+
upstatic deploy
|
36
|
+
|
37
|
+
For more configuration options, keep reading.
|
38
|
+
|
39
|
+
## Configuration
|
40
|
+
|
41
|
+
### Gzip
|
42
|
+
|
43
|
+
If you want to gzip certain files, just specify which file extensions
|
44
|
+
you'd like gzipped. Note that gzipping files means that they'll always
|
45
|
+
be served compressed. S3 does not support serving different files based
|
46
|
+
on the Accept-Encoding header.
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
gzip_extensions ".html", ".js", ".css"
|
50
|
+
```
|
51
|
+
|
52
|
+
By default no files are gzipped.
|
53
|
+
|
54
|
+
### Cache-Control headers
|
55
|
+
|
56
|
+
You can also specify Cache-Control headers for specific file extensions.
|
57
|
+
For example, you might want Cloudfront (and proxies) to cache HTML files
|
58
|
+
for 600 seconds, but tell the browser not to cache them:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
cache_control ".html", "public, s-maxage=600, max-age=0"
|
62
|
+
```
|
63
|
+
|
64
|
+
You can also specify the default for all other files:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
default_cache_control "public, max-age=691200"
|
68
|
+
```
|
69
|
+
|
70
|
+
The default is to serve every file with `Cache-Control: public,
|
71
|
+
max-age=691200`.
|
72
|
+
|
73
|
+
### Cloudfront
|
74
|
+
|
75
|
+
If you are serving some or all your files from Cloudfront, you probably
|
76
|
+
want to invalidate the newly updated objects. Just specify your
|
77
|
+
Cloudfront distribution ID and it will be taken care of when you deploy.
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
distribution_id "E2ADVMBKHEFZJ3"
|
81
|
+
```
|
82
|
+
|
83
|
+
## Notes
|
84
|
+
|
85
|
+
This extension keeps track of every deployed file in a `.sha1sums` at
|
86
|
+
the root of the bucket. This is a private file that contains a SHA-1 sum
|
87
|
+
of the uploaded file, followed by a SHA-1 sum of its upload options and
|
88
|
+
finally the filename. This file is used to know if a file needs to be
|
89
|
+
uploaded again.
|
90
|
+
|
91
|
+
## Testing
|
92
|
+
|
93
|
+
Everything is tested by actually deploying to S3 and requesting the
|
94
|
+
deployed files.
|
95
|
+
|
96
|
+
## Contributing
|
97
|
+
|
98
|
+
1. Fork it
|
99
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
100
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
101
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
102
|
+
5. Create new Pull Request
|
103
|
+
|
104
|
+
## License
|
105
|
+
|
106
|
+
MIT License.
|
data/Rakefile
ADDED
data/bin/upstatic
ADDED
data/lib/upstatic.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "upstatic/compat"
|
2
|
+
require "upstatic/configuration"
|
3
|
+
require "upstatic/deployer"
|
4
|
+
require "upstatic/manager"
|
5
|
+
require "upstatic/shell"
|
6
|
+
|
7
|
+
module Upstatic
|
8
|
+
def self.shell
|
9
|
+
@@shell ||= Upstatic::Shell.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.shell=(shell)
|
13
|
+
@@shell = shell
|
14
|
+
end
|
15
|
+
end
|
data/lib/upstatic/cli.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "upstatic"
|
2
|
+
require "thor"
|
3
|
+
|
4
|
+
module Upstatic
|
5
|
+
class CLI < Thor
|
6
|
+
include Thor::Actions
|
7
|
+
|
8
|
+
desc "deploy", "Deploy to S3"
|
9
|
+
def deploy
|
10
|
+
Deployer.new(configuration).deploy!
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def configuration
|
16
|
+
@configuration ||= Configuration.read("./Upstatic")
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
unless SecureRandom.respond_to?(:uuid)
|
4
|
+
module SecureRandom
|
5
|
+
def self.uuid
|
6
|
+
ary = self.random_bytes(16).unpack("NnnnnN")
|
7
|
+
ary[2] = (ary[2] & 0x0fff) | 0x4000
|
8
|
+
ary[3] = (ary[3] & 0x3fff) | 0x8000
|
9
|
+
"%08x-%04x-%04x-%04x-%04x%08x" % ary
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require "upstatic/options"
|
2
|
+
|
3
|
+
module Upstatic
|
4
|
+
class Configuration
|
5
|
+
include Options
|
6
|
+
|
7
|
+
option :bucket
|
8
|
+
option :region
|
9
|
+
option :access_key_id
|
10
|
+
option :secret_access_key
|
11
|
+
option :distribution_id
|
12
|
+
option :default_cache_control, :default => "public, max-age=691200"
|
13
|
+
option :upload_dir, :default => "."
|
14
|
+
option :force_deploy, :default => false
|
15
|
+
|
16
|
+
def gzip_extensions(*extensions)
|
17
|
+
if extensions.count > 0
|
18
|
+
@gzip_extensions = extensions.map { |ext| sanitize_extension(ext) }
|
19
|
+
else
|
20
|
+
@gzip_extensions ||= []
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def cache_controls
|
25
|
+
@cache_controls ||= {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def cache_control(extension, cache_control)
|
29
|
+
cache_controls[sanitize_extension(extension)] = cache_control
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.read(path)
|
33
|
+
config = new
|
34
|
+
config.instance_eval(File.read(path), path, 1)
|
35
|
+
config
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def sanitize_extension(extension)
|
41
|
+
extension.to_s.sub(/\A([^.])/, '.\1')
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
require "mime/types"
|
3
|
+
require "zlib"
|
4
|
+
require "stringio"
|
5
|
+
require "upstatic/tool"
|
6
|
+
require "upstatic/manager"
|
7
|
+
require "upstatic/verifier"
|
8
|
+
|
9
|
+
module Upstatic
|
10
|
+
class Deployer < Tool
|
11
|
+
FORBIDDEN_FILENAMES = ["Upstatic"]
|
12
|
+
|
13
|
+
def deploy!
|
14
|
+
manager.create_bucket
|
15
|
+
manager.configure_bucket
|
16
|
+
|
17
|
+
Dir.chdir config.upload_dir do
|
18
|
+
Dir["**/*"].each do |filename|
|
19
|
+
upload(filename)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
perform_invalidation
|
24
|
+
verifier.write_checksum_file
|
25
|
+
|
26
|
+
@verifier = nil
|
27
|
+
@invalidations = nil
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def manager
|
33
|
+
@manager ||= Manager.new(config)
|
34
|
+
end
|
35
|
+
|
36
|
+
def verifier
|
37
|
+
@verifier ||= Verifier.new(config)
|
38
|
+
end
|
39
|
+
|
40
|
+
def invalidations
|
41
|
+
@invalidations ||= Set.new
|
42
|
+
end
|
43
|
+
|
44
|
+
def mark_to_invalidate(filename)
|
45
|
+
if config.distribution_id
|
46
|
+
invalidate_path("/#{filename}")
|
47
|
+
|
48
|
+
if File.basename(filename) == "index.html"
|
49
|
+
dirname = File.dirname(filename)
|
50
|
+
|
51
|
+
if dirname == "."
|
52
|
+
invalidate_path("/")
|
53
|
+
else
|
54
|
+
invalidate_path("/#{dirname}")
|
55
|
+
invalidate_path("/#{dirname}/")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def invalidate_path(path)
|
62
|
+
if invalidations.add?(path)
|
63
|
+
shell.say_status("invalidate", path, :cyan)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def options_for_file(filename)
|
68
|
+
extension = File.extname(filename)
|
69
|
+
mime_type = MIME::Types.type_for(filename).first
|
70
|
+
content_type = mime_type ? mime_type.content_type : nil
|
71
|
+
|
72
|
+
file_options = {
|
73
|
+
:file => filename,
|
74
|
+
:acl => :public_read,
|
75
|
+
:content_type => content_type,
|
76
|
+
:cache_control => config.default_cache_control
|
77
|
+
}
|
78
|
+
|
79
|
+
if cache_control = config.cache_controls[extension]
|
80
|
+
file_options[:cache_control] = cache_control
|
81
|
+
end
|
82
|
+
|
83
|
+
if gzip_file?(filename)
|
84
|
+
file_options.delete(:file)
|
85
|
+
file_options[:content_encoding] = "gzip"
|
86
|
+
end
|
87
|
+
|
88
|
+
file_options
|
89
|
+
end
|
90
|
+
|
91
|
+
def gzip_file?(filename)
|
92
|
+
extension = File.extname(filename)
|
93
|
+
config.gzip_extensions.include?(extension)
|
94
|
+
end
|
95
|
+
|
96
|
+
def gzipped_file(filename)
|
97
|
+
shell.say_status("gzip", filename, :yellow)
|
98
|
+
|
99
|
+
io = StringIO.new("w")
|
100
|
+
gz = Zlib::GzipWriter.new(io)
|
101
|
+
gz.write(File.read(filename))
|
102
|
+
gz.close
|
103
|
+
|
104
|
+
io.string
|
105
|
+
end
|
106
|
+
|
107
|
+
def upload(filename)
|
108
|
+
return unless File.file?(filename)
|
109
|
+
return if FORBIDDEN_FILENAMES.include?(filename)
|
110
|
+
|
111
|
+
options = options_for_file(filename)
|
112
|
+
|
113
|
+
if verifier.stale?(filename, options)
|
114
|
+
params = [options_for_file(filename)]
|
115
|
+
|
116
|
+
if gzip_file?(filename)
|
117
|
+
data = gzipped_file(filename)
|
118
|
+
params.unshift(data)
|
119
|
+
end
|
120
|
+
|
121
|
+
shell.say_status("upload", filename, :green)
|
122
|
+
bucket.objects[filename].write(*params)
|
123
|
+
mark_to_invalidate(filename)
|
124
|
+
else
|
125
|
+
shell.say_status("identical", filename, :blue)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def perform_invalidation
|
130
|
+
return unless config.distribution_id
|
131
|
+
return if invalidations.empty?
|
132
|
+
|
133
|
+
cf.client.create_invalidation(
|
134
|
+
:distribution_id => config.distribution_id,
|
135
|
+
:invalidation_batch => {
|
136
|
+
:paths => {
|
137
|
+
:quantity => invalidations.count,
|
138
|
+
:items => invalidations.to_a
|
139
|
+
},
|
140
|
+
:caller_reference => SecureRandom.uuid
|
141
|
+
}
|
142
|
+
)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "upstatic/tool"
|
2
|
+
|
3
|
+
module Upstatic
|
4
|
+
class Manager < Tool
|
5
|
+
|
6
|
+
def create_bucket
|
7
|
+
unless bucket.exists?
|
8
|
+
shell.say_status "bucket", "creating '#{config.bucket}'"
|
9
|
+
s3.buckets.create(config.bucket)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def configure_bucket
|
14
|
+
unless bucket.website_configuration
|
15
|
+
shell.say_status "bucket", "configuring for website hosting"
|
16
|
+
bucket.configure_website
|
17
|
+
end
|
18
|
+
rescue AWS::S3::Errors::NoSuchBucket
|
19
|
+
# Can't find the newly created bucket yet. Let's wait a bit and retry.
|
20
|
+
sleep(2)
|
21
|
+
retry
|
22
|
+
rescue AWS::S3::Errors::AccessDenied
|
23
|
+
abort "We could not access the bucket '#{config.bucket}'.\n\n" <<
|
24
|
+
"One of two things happened here:\n\n" <<
|
25
|
+
"- You're trying to access a bucket that another user already\n" <<
|
26
|
+
" owns. Please note that the bucket namespace is shared by\n" <<
|
27
|
+
" all users of the system.\n" <<
|
28
|
+
"- You actually own this bucket on some other account. If that\n" <<
|
29
|
+
" is the case, please check your credentials."
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Upstatic
|
2
|
+
|
3
|
+
# The +Options+ module can be mixed-in with any other class to provide some
|
4
|
+
# nice helpers for adding options with default values.
|
5
|
+
#
|
6
|
+
# This is used internally in +Configuration+:
|
7
|
+
#
|
8
|
+
# class Configuration
|
9
|
+
# include Options
|
10
|
+
# option :bucket
|
11
|
+
# end
|
12
|
+
module Options
|
13
|
+
|
14
|
+
def self.included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# Allows the definition of options in a similar way to +attr_accessor+,
|
21
|
+
# except that the same method acts as getter and setter, depending on
|
22
|
+
# whether an argument is passed (setter) or not (getter).
|
23
|
+
#
|
24
|
+
# An optional hash can be passed as a second argument, to specify
|
25
|
+
# additional options such as a default value using +:default+.
|
26
|
+
def option(name, options={})
|
27
|
+
define_method(name) do |*args|
|
28
|
+
if args.size > 0
|
29
|
+
instance_variable_set("@#{name}", args.first)
|
30
|
+
else
|
31
|
+
if instance_variable_defined?("@#{name}")
|
32
|
+
instance_variable_get("@#{name}")
|
33
|
+
elsif options.keys.include?(:default)
|
34
|
+
options[:default]
|
35
|
+
else
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "aws-sdk"
|
2
|
+
require "upstatic/configuration"
|
3
|
+
|
4
|
+
module Upstatic
|
5
|
+
class Tool
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
@configuration = configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def config
|
14
|
+
@configuration
|
15
|
+
end
|
16
|
+
|
17
|
+
def s3
|
18
|
+
@s3 ||= AWS::S3.new(
|
19
|
+
:access_key_id => config.access_key_id,
|
20
|
+
:secret_access_key => config.secret_access_key,
|
21
|
+
:region => config.region
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def bucket
|
26
|
+
@bucket ||= s3.buckets[config.bucket]
|
27
|
+
end
|
28
|
+
|
29
|
+
def cf
|
30
|
+
@cf ||= AWS::CloudFront.new(
|
31
|
+
:access_key_id => config.access_key_id,
|
32
|
+
:secret_access_key => config.secret_access_key
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
def shell
|
37
|
+
Upstatic.shell
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "digest/sha1"
|
2
|
+
require "upstatic/tool"
|
3
|
+
|
4
|
+
module Upstatic
|
5
|
+
class Verifier < Tool
|
6
|
+
|
7
|
+
def initialize(configuration)
|
8
|
+
super
|
9
|
+
|
10
|
+
@files = {}
|
11
|
+
@file_options = {}
|
12
|
+
|
13
|
+
@new_files = {}
|
14
|
+
@new_file_options = {}
|
15
|
+
|
16
|
+
bucket.objects[".sha1sums"].read.lines.each do |line|
|
17
|
+
file_checksum, file_options_checksum, filename = line.split(" ", 3)
|
18
|
+
filename.chomp!
|
19
|
+
@files[filename] = file_checksum
|
20
|
+
@file_options[filename] = file_options_checksum
|
21
|
+
end
|
22
|
+
rescue AWS::S3::Errors::NoSuchKey
|
23
|
+
# Checksum file doesn't exist.
|
24
|
+
# Both @files and @file_options will be empty, and that's ok.
|
25
|
+
@has_no_data = true
|
26
|
+
end
|
27
|
+
|
28
|
+
def stale?(filename, write_options)
|
29
|
+
return true if config.force_deploy
|
30
|
+
|
31
|
+
file_options_checksum = Digest::SHA1.hexdigest(Marshal.dump(write_options))
|
32
|
+
file_checksum = Digest::SHA1.file(filename).hexdigest
|
33
|
+
|
34
|
+
@new_files[filename] = file_checksum
|
35
|
+
@new_file_options[filename] = file_options_checksum
|
36
|
+
|
37
|
+
if @files[filename] != file_checksum
|
38
|
+
return true
|
39
|
+
end
|
40
|
+
|
41
|
+
if @file_options[filename] != file_options_checksum
|
42
|
+
return true
|
43
|
+
end
|
44
|
+
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_checksum_file
|
49
|
+
lines = []
|
50
|
+
@new_files.each do |filename, checksum|
|
51
|
+
file_option_checksum = @new_file_options[filename]
|
52
|
+
lines << "#{checksum} #{file_option_checksum} #{filename}"
|
53
|
+
end
|
54
|
+
|
55
|
+
shell.say_status("upload", ".sha1sums", :green)
|
56
|
+
bucket.objects[".sha1sums"].write(lines.join("\n"))
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
|
3
|
+
class TestCacheControl < Upstatic::TestCase
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
return @config if @config
|
7
|
+
|
8
|
+
@config = Upstatic::Configuration.new
|
9
|
+
@config.default_cache_control "public, no-cache"
|
10
|
+
@config.cache_control ".css", "public, max-age=3600"
|
11
|
+
@config.cache_control ".js", "public, max-age=0"
|
12
|
+
@config
|
13
|
+
end
|
14
|
+
|
15
|
+
def config
|
16
|
+
self.class.config
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup
|
20
|
+
super
|
21
|
+
deploy :cache_control, config
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_sets_cache_control_for_css_file
|
25
|
+
response = request("styles.css")
|
26
|
+
assert_equal "public, max-age=3600", response["Cache-Control"]
|
27
|
+
end
|
28
|
+
|
29
|
+
def test_sets_cache_control_for_css_file
|
30
|
+
response = request("scripts.js")
|
31
|
+
assert_equal "public, max-age=0", response["Cache-Control"]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_sets_default_cache_control_for_other_files
|
35
|
+
response = request("index.html")
|
36
|
+
assert_equal "public, no-cache", response["Cache-Control"]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
|
3
|
+
module Upstatic
|
4
|
+
class TestCase < Minitest::Test
|
5
|
+
REGION = "eu-west-1"
|
6
|
+
ORIG_UPLOAD_DIR = File.expand_path("../../upload_dir", __FILE__)
|
7
|
+
TEST_UPLOAD_DIR = File.expand_path("../../upload_dir_test", __FILE__)
|
8
|
+
|
9
|
+
def setup
|
10
|
+
FileUtils.rm_rf(TEST_UPLOAD_DIR)
|
11
|
+
FileUtils.cp_r(ORIG_UPLOAD_DIR, TEST_UPLOAD_DIR)
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
FileUtils.rm_rf(TEST_UPLOAD_DIR)
|
16
|
+
end
|
17
|
+
|
18
|
+
def request(path, headers={})
|
19
|
+
uri = URI("http://#{@@last_deployment}.s3-website-#{REGION}.amazonaws.com/#{path}")
|
20
|
+
req = Net::HTTP::Get.new(uri.path)
|
21
|
+
headers.each { |k,v| req[k] = v }
|
22
|
+
Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def deployments
|
26
|
+
@@deployments ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def deploy(identifier, configuration, force=false)
|
30
|
+
deployment = deployments[identifier]
|
31
|
+
|
32
|
+
if !deployment || force
|
33
|
+
unless configuration.bucket
|
34
|
+
configuration.bucket "upstatic-test-#{SecureRandom.uuid}"
|
35
|
+
end
|
36
|
+
|
37
|
+
configuration.upload_dir TEST_UPLOAD_DIR
|
38
|
+
configuration.region REGION
|
39
|
+
|
40
|
+
Upstatic::Deployer.new(configuration).deploy!
|
41
|
+
@@last_deployment = configuration.bucket
|
42
|
+
deployments[identifier] = configuration.bucket
|
43
|
+
else
|
44
|
+
deployment
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def write_file(path, content)
|
49
|
+
File.open(path, "w") { |f| f.write(content) }
|
50
|
+
end
|
51
|
+
|
52
|
+
def assert_gzipped(string)
|
53
|
+
msg = "Expected string to be gzipped"
|
54
|
+
assert string.bytes.first(2) == [0x1f, 0x8b], msg
|
55
|
+
end
|
56
|
+
|
57
|
+
def refute_gzipped(string)
|
58
|
+
msg = "Expected string to not be gzipped"
|
59
|
+
assert string.bytes.first(2) != [0x1f, 0x8b], msg
|
60
|
+
end
|
61
|
+
|
62
|
+
def assert_request_count(method, expected_count, &block)
|
63
|
+
request_count = 0
|
64
|
+
|
65
|
+
WebMock.after_request do |request_signature, response|
|
66
|
+
request_count += 1 if request_signature.method == method
|
67
|
+
end
|
68
|
+
|
69
|
+
block.call
|
70
|
+
|
71
|
+
WebMock.reset_callbacks
|
72
|
+
|
73
|
+
assert_equal expected_count, request_count
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
|
3
|
+
class TestDeployment < Upstatic::TestCase
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
return @config if @config
|
7
|
+
@config = Upstatic::Configuration.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def config
|
11
|
+
self.class.config
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
super
|
16
|
+
deploy :default, config
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_deploys_html_file
|
20
|
+
response = request("index.html")
|
21
|
+
assert_equal "200", response.code
|
22
|
+
assert_equal "UPSTATIC_HTML", response.body.strip
|
23
|
+
assert_equal "text/html", response["Content-Type"]
|
24
|
+
assert_equal "public, max-age=691200", response["Cache-Control"]
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_deploys_css_file
|
28
|
+
response = request("styles.css")
|
29
|
+
assert_equal "200", response.code
|
30
|
+
assert_equal "UPSTATIC_CSS", response.body.strip
|
31
|
+
assert_equal "text/css", response["Content-Type"]
|
32
|
+
assert_equal "public, max-age=691200", response["Cache-Control"]
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_deploys_javascript_file
|
36
|
+
response = request("scripts.js")
|
37
|
+
assert_equal "200", response.code
|
38
|
+
assert_equal "UPSTATIC_JS", response.body.strip
|
39
|
+
assert_equal "application/javascript", response["Content-Type"]
|
40
|
+
assert_equal "public, max-age=691200", response["Cache-Control"]
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_deploys_folder
|
44
|
+
response = request("folder/index.html")
|
45
|
+
assert_equal "200", response.code
|
46
|
+
assert_equal "UPSTATIC_FOLDER", response.body.strip
|
47
|
+
assert_equal "text/html", response["Content-Type"]
|
48
|
+
assert_equal "public, max-age=691200", response["Cache-Control"]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_does_not_make_checksum_file_public
|
52
|
+
response = request(".sha1sums")
|
53
|
+
assert_equal "403", response.code
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_does_not_deploy_config_file
|
57
|
+
response = request("Upstatic")
|
58
|
+
assert_equal "403", response.code
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
|
3
|
+
class TestGzip < Upstatic::TestCase
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
return @config if @config
|
7
|
+
|
8
|
+
@config = Upstatic::Configuration.new
|
9
|
+
@config.gzip_extensions ".html", ".css"
|
10
|
+
@config
|
11
|
+
end
|
12
|
+
|
13
|
+
def config
|
14
|
+
self.class.config
|
15
|
+
end
|
16
|
+
|
17
|
+
def request(uri, headers={})
|
18
|
+
headers.merge!("Accept-Encoding" => "identity")
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def setup
|
23
|
+
super
|
24
|
+
deploy :gzip, config
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_gzips_specified_files
|
28
|
+
response = request("index.html")
|
29
|
+
assert_gzipped response.body
|
30
|
+
assert_equal "gzip", response["Content-Encoding"]
|
31
|
+
|
32
|
+
response = request("folder/index.html")
|
33
|
+
assert_gzipped response.body
|
34
|
+
assert_equal "gzip", response["Content-Encoding"]
|
35
|
+
|
36
|
+
response = request("styles.css")
|
37
|
+
assert_gzipped response.body
|
38
|
+
assert_equal "gzip", response["Content-Encoding"]
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_does_not_gzip_other_files
|
42
|
+
response = request("scripts.js")
|
43
|
+
refute_gzipped response.body
|
44
|
+
assert_nil response["Content-Encoding"]
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_compresses_content_correctly
|
48
|
+
response = request("index.html")
|
49
|
+
|
50
|
+
io = StringIO.new(response.body)
|
51
|
+
gz = Zlib::GzipReader.new(io)
|
52
|
+
|
53
|
+
assert_equal "UPSTATIC_HTML", gz.read.strip
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
|
3
|
+
class TestInvalidations < Upstatic::TestCase
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
return @config if @config
|
7
|
+
@config = Upstatic::Configuration.new
|
8
|
+
@config.distribution_id "EX7C5EKLRKVC7"
|
9
|
+
@config
|
10
|
+
end
|
11
|
+
|
12
|
+
def config
|
13
|
+
self.class.config
|
14
|
+
end
|
15
|
+
|
16
|
+
def last_invalidation_id
|
17
|
+
AWS::CloudFront.new.client.list_invalidations(
|
18
|
+
:distribution_id => config.distribution_id,
|
19
|
+
:max_items => 1
|
20
|
+
)[:items][0][:id]
|
21
|
+
end
|
22
|
+
|
23
|
+
def last_invalidation
|
24
|
+
AWS::CloudFront.new.client.get_invalidation(
|
25
|
+
:distribution_id => config.distribution_id,
|
26
|
+
:id => last_invalidation_id
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
def last_invalidated_items
|
31
|
+
last_invalidation[:invalidation_batch][:paths][:items]
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_creates_invalidation_rules
|
35
|
+
deploy :invalidations, config
|
36
|
+
|
37
|
+
paths = last_invalidated_items
|
38
|
+
|
39
|
+
assert_equal 7, paths.count
|
40
|
+
|
41
|
+
assert_includes paths, "/"
|
42
|
+
assert_includes paths, "/index.html"
|
43
|
+
assert_includes paths, "/scripts.js"
|
44
|
+
assert_includes paths, "/styles.css"
|
45
|
+
assert_includes paths, "/folder"
|
46
|
+
assert_includes paths, "/folder/"
|
47
|
+
assert_includes paths, "/folder/index.html"
|
48
|
+
|
49
|
+
changed_file = File.join(TEST_UPLOAD_DIR, "scripts.js")
|
50
|
+
write_file(changed_file, "UPSTATIC_CHANGED")
|
51
|
+
|
52
|
+
new_config = config.dup
|
53
|
+
new_config.cache_control ".css", "private, max-age=0"
|
54
|
+
|
55
|
+
deploy :invalidations, new_config, true
|
56
|
+
|
57
|
+
paths = last_invalidated_items
|
58
|
+
|
59
|
+
assert_equal 2, paths.count
|
60
|
+
assert_includes paths, "/scripts.js"
|
61
|
+
assert_includes paths, "/styles.css"
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "cases/helper"
|
2
|
+
|
3
|
+
class TestMultipleDeployments < Upstatic::TestCase
|
4
|
+
|
5
|
+
def self.config
|
6
|
+
return @config if @config
|
7
|
+
@config = Upstatic::Configuration.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def config
|
11
|
+
self.class.config
|
12
|
+
end
|
13
|
+
|
14
|
+
def setup
|
15
|
+
super
|
16
|
+
config.bucket nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_do_not_upload_unchanged_files
|
20
|
+
deploy :multiple_unchanged, config
|
21
|
+
|
22
|
+
assert_request_count(:put, 1) do
|
23
|
+
deploy :multiple_unchanged, config, true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_upload_changed_files
|
28
|
+
deploy :multiple_changed, config
|
29
|
+
|
30
|
+
changed_file = File.join(TEST_UPLOAD_DIR, "folder/index.html")
|
31
|
+
write_file(changed_file, "UPSTATIC_CHANGED")
|
32
|
+
|
33
|
+
assert_request_count(:put, 2) do
|
34
|
+
deploy :multiple_changed, config, true
|
35
|
+
end
|
36
|
+
|
37
|
+
response = request("folder/index.html")
|
38
|
+
assert_equal "UPSTATIC_CHANGED", response.body.strip
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_upload_added_files
|
42
|
+
deploy :multiple_added, config
|
43
|
+
|
44
|
+
new_file = File.join(TEST_UPLOAD_DIR, "new.html")
|
45
|
+
write_file(new_file, "UPSTATIC_NEW")
|
46
|
+
|
47
|
+
assert_request_count(:put, 2) do
|
48
|
+
deploy :multiple_added, config, true
|
49
|
+
end
|
50
|
+
|
51
|
+
response = request("new.html")
|
52
|
+
assert_equal "UPSTATIC_NEW", response.body.strip
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_upload_modified_configuration_files
|
56
|
+
deploy :muliple_config, config
|
57
|
+
|
58
|
+
new_config = config.dup
|
59
|
+
new_config.gzip_extensions ".html"
|
60
|
+
|
61
|
+
assert_request_count(:put, 3) do
|
62
|
+
deploy :multiple_config, new_config, true
|
63
|
+
end
|
64
|
+
|
65
|
+
response = request("index.html", "Accept-Encoding" => "identity")
|
66
|
+
assert_gzipped response.body
|
67
|
+
assert_equal "gzip", response["Content-Encoding"]
|
68
|
+
|
69
|
+
response = request("folder/index.html", "Accept-Encoding" => "identity")
|
70
|
+
assert_gzipped response.body
|
71
|
+
assert_equal "gzip", response["Content-Encoding"]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
SimpleCov.start do
|
3
|
+
add_filter "/test/"
|
4
|
+
add_filter "/lib/upstatic/compat.rb"
|
5
|
+
end
|
6
|
+
|
7
|
+
$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
|
8
|
+
require "upstatic"
|
9
|
+
|
10
|
+
require "minitest/autorun"
|
11
|
+
require "webmock/minitest"
|
12
|
+
require "upstatic/quiet_shell"
|
13
|
+
|
14
|
+
Minitest.after_run do
|
15
|
+
AWS::S3.new.buckets.each { |b| b.delete! if b.name =~ /^upstatic-test-/ }
|
16
|
+
end
|
17
|
+
|
18
|
+
WebMock.allow_net_connect!
|
19
|
+
|
20
|
+
Upstatic.shell = Upstatic::QuietShell.new
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "minitest_helper"
|
2
|
+
|
3
|
+
class TestConfiguration < Minitest::Test
|
4
|
+
|
5
|
+
def setup
|
6
|
+
super
|
7
|
+
@config = Upstatic::Configuration.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_reads_from_file
|
11
|
+
path = File.expand_path("../upload_dir/Upstatic", __FILE__)
|
12
|
+
config = Upstatic::Configuration.read(path)
|
13
|
+
assert_equal "upstatic", config.bucket
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_cache_control_sanitizes_extensions
|
17
|
+
@config.cache_control "html", "public"
|
18
|
+
assert_equal "public", @config.cache_controls[".html"]
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_cache_control_accepts_symbols_as_extensions
|
22
|
+
@config.cache_control :css, "public"
|
23
|
+
assert_equal "public", @config.cache_controls[".css"]
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_gzip_extensions_sanitizes_extensions
|
27
|
+
@config.gzip_extensions :html, "css", ".js"
|
28
|
+
assert @config.gzip_extensions.include?(".html")
|
29
|
+
assert @config.gzip_extensions.include?(".css")
|
30
|
+
assert @config.gzip_extensions.include?(".js")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
bucket "upstatic"
|
@@ -0,0 +1 @@
|
|
1
|
+
UPSTATIC_FOLDER
|
@@ -0,0 +1 @@
|
|
1
|
+
UPSTATIC_HTML
|
@@ -0,0 +1 @@
|
|
1
|
+
UPSTATIC_JS
|
@@ -0,0 +1 @@
|
|
1
|
+
UPSTATIC_CSS
|
data/upstatic.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'upstatic/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "upstatic"
|
8
|
+
spec.version = Upstatic::VERSION
|
9
|
+
spec.authors = ["Joao Carlos"]
|
10
|
+
spec.email = ["mail@joao-carlos.com"]
|
11
|
+
spec.description = %q{Upstatic lets you deploy your static sites on AWS}
|
12
|
+
spec.summary = %q{Deploy static sites}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
|
23
|
+
spec.add_dependency "aws-sdk"
|
24
|
+
spec.add_dependency "thor"
|
25
|
+
spec.add_dependency "mime-types"
|
26
|
+
end
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: upstatic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Joao Carlos
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-05-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: aws-sdk
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: thor
|
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
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: mime-types
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
description: Upstatic lets you deploy your static sites on AWS
|
70
|
+
email:
|
71
|
+
- mail@joao-carlos.com
|
72
|
+
executables:
|
73
|
+
- upstatic
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- ".gitignore"
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- bin/upstatic
|
83
|
+
- lib/upstatic.rb
|
84
|
+
- lib/upstatic/cli.rb
|
85
|
+
- lib/upstatic/compat.rb
|
86
|
+
- lib/upstatic/configuration.rb
|
87
|
+
- lib/upstatic/deployer.rb
|
88
|
+
- lib/upstatic/manager.rb
|
89
|
+
- lib/upstatic/options.rb
|
90
|
+
- lib/upstatic/quiet_shell.rb
|
91
|
+
- lib/upstatic/shell.rb
|
92
|
+
- lib/upstatic/tool.rb
|
93
|
+
- lib/upstatic/verifier.rb
|
94
|
+
- lib/upstatic/version.rb
|
95
|
+
- test/cases/helper.rb
|
96
|
+
- test/cases/test_cache_control.rb
|
97
|
+
- test/cases/test_case.rb
|
98
|
+
- test/cases/test_deployment.rb
|
99
|
+
- test/cases/test_gzip.rb
|
100
|
+
- test/cases/test_invalidations.rb
|
101
|
+
- test/cases/test_multiple_deployments.rb
|
102
|
+
- test/minitest_helper.rb
|
103
|
+
- test/test_configuration.rb
|
104
|
+
- test/upload_dir/Upstatic
|
105
|
+
- test/upload_dir/folder/index.html
|
106
|
+
- test/upload_dir/index.html
|
107
|
+
- test/upload_dir/scripts.js
|
108
|
+
- test/upload_dir/styles.css
|
109
|
+
- upstatic.gemspec
|
110
|
+
homepage: ''
|
111
|
+
licenses:
|
112
|
+
- MIT
|
113
|
+
metadata: {}
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
require_paths:
|
117
|
+
- lib
|
118
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: 1.3.1
|
128
|
+
requirements: []
|
129
|
+
rubyforge_project:
|
130
|
+
rubygems_version: 2.2.2
|
131
|
+
signing_key:
|
132
|
+
specification_version: 4
|
133
|
+
summary: Deploy static sites
|
134
|
+
test_files:
|
135
|
+
- test/cases/helper.rb
|
136
|
+
- test/cases/test_cache_control.rb
|
137
|
+
- test/cases/test_case.rb
|
138
|
+
- test/cases/test_deployment.rb
|
139
|
+
- test/cases/test_gzip.rb
|
140
|
+
- test/cases/test_invalidations.rb
|
141
|
+
- test/cases/test_multiple_deployments.rb
|
142
|
+
- test/minitest_helper.rb
|
143
|
+
- test/test_configuration.rb
|
144
|
+
- test/upload_dir/Upstatic
|
145
|
+
- test/upload_dir/folder/index.html
|
146
|
+
- test/upload_dir/index.html
|
147
|
+
- test/upload_dir/scripts.js
|
148
|
+
- test/upload_dir/styles.css
|
149
|
+
has_rdoc:
|