asset_manifest 1.0.0.pre.rc1
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/LICENSE +22 -0
- data/README.md +99 -0
- data/bin/asset-manifest +38 -0
- data/lib/asset_manifest/cuba.rb +59 -0
- data/lib/asset_manifest/version.rb +3 -0
- data/lib/asset_manifest.rb +165 -0
- metadata +94 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 60c26b354f46bb76f50c8d4a1cbec0b81b34584a
|
4
|
+
data.tar.gz: 19c9d5a20eded7714f188233edcd5d920d7a7b5d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9b2f14e2cdae4e98842eb232089c9c38be813e24819b151a7e0f2a5e37c4e599334f106f58077d70ac2471b5620ea61c1d2a16f3b9e22aa03f7201064915ea05
|
7
|
+
data.tar.gz: 81fba360287012e033e387c41a3ccabef024c8b20a17c8e3c6029cf0c6970c5ca2f38d16f2cc0db4c518683697cf8c66329bff8eb3bba9e27d1071e909694e1e
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2016 Nicolas Sanguinetti <foca@13floor.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# Asset Manifest
|
2
|
+
|
3
|
+
Tiny set of utilities to compute subresource integrity and cache busting
|
4
|
+
checksums for static assets.
|
5
|
+
|
6
|
+
## How it works
|
7
|
+
|
8
|
+
Asset Manifest provides two main utilities:
|
9
|
+
|
10
|
+
* An executable that you should run when compiling your static assets. This
|
11
|
+
generates a `manifest` file that includes the SRI hash and a checksum (for
|
12
|
+
cache-busting purposes) of each of your assets.
|
13
|
+
|
14
|
+
* A ruby library to generate the proper links to the assets taking into account
|
15
|
+
the data from your manifest.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
Somewhere in your makefile, you'll want to generate the `public/manifest.json`
|
20
|
+
file like so:
|
21
|
+
|
22
|
+
``` Makefile
|
23
|
+
# Assuming you keep an ASSETS list with all the assets you're compiling...
|
24
|
+
ASSETS += public/css/app.css
|
25
|
+
ASSETS += public/css/app.min.css
|
26
|
+
ASSETS += public/js/app.js
|
27
|
+
ASSETS += public/js/app.min.js
|
28
|
+
|
29
|
+
# ...you'd want a rule like this one:
|
30
|
+
public/manifest.json: $(ASSETS)
|
31
|
+
asset-manifest -d public $^ > $@
|
32
|
+
```
|
33
|
+
|
34
|
+
Then, in your app, you'll want to initialize `AssetManifest::Helpers` passing
|
35
|
+
the contents of this JSON file:
|
36
|
+
|
37
|
+
``` ruby
|
38
|
+
assets = AssetManifest::Helpers.new(
|
39
|
+
JSON.parse(File.read("./public/manifest.json")),
|
40
|
+
{ minify: ENV["RACK_ENV"] == "production" }
|
41
|
+
)
|
42
|
+
```
|
43
|
+
|
44
|
+
Finally, you'll want to pass this object to your views, so that you can do the
|
45
|
+
following:
|
46
|
+
|
47
|
+
``` erb
|
48
|
+
<%= assets.stylesheet_tag("/css/app.css") %>
|
49
|
+
<%= assets.script_tag("/js/app.js") %>
|
50
|
+
```
|
51
|
+
|
52
|
+
This would produce output similar to:
|
53
|
+
|
54
|
+
``` html
|
55
|
+
<link rel="stylesheet"
|
56
|
+
href="/css/app-07915293e2bb992a67c618e1aa335d978efc3734.css"
|
57
|
+
integrity="sha256-yqqr5VJwz1IM5iTlSram51zrBvuE21FbiYgNnD2fwgE=">
|
58
|
+
<script src="/js/app-1a4aefaba81b61c7ea763d42fcb39584e5784c32.js"
|
59
|
+
integrity="sha256-ZmTdnjlqI4ppv9cW8Y5i6PixtV4CQlVUvxB2iySwU94="></script>
|
60
|
+
```
|
61
|
+
|
62
|
+
(Whitespace added for clarity)
|
63
|
+
|
64
|
+
### With Rake instead of Make
|
65
|
+
|
66
|
+
Assuming you have an `ASSETS` constant with the list of assets, you can use the
|
67
|
+
following example in your `Rakefile` to re-generate your manifest when your
|
68
|
+
assets change.
|
69
|
+
|
70
|
+
``` rake
|
71
|
+
rule "public/manifest.json" => ASSETS do |t|
|
72
|
+
sh "asset-manifest -d public #{t.prerequisites.join(" ")} > #{t.name}"
|
73
|
+
end
|
74
|
+
|
75
|
+
task assets: ASSETS
|
76
|
+
task assets: "public/manifest.json"
|
77
|
+
```
|
78
|
+
|
79
|
+
You can add this file to your `assets` so that the file is automatically
|
80
|
+
generated when your source assets change.
|
81
|
+
|
82
|
+
## Cuba plugin
|
83
|
+
|
84
|
+
If you use [Cuba](http://cuba.is), then you can just do this:
|
85
|
+
|
86
|
+
``` ruby
|
87
|
+
require "asset_manifest/cuba"
|
88
|
+
Cuba.plugin AssetManifest::Cuba
|
89
|
+
```
|
90
|
+
|
91
|
+
This will set up an `assets` helper available in all your apps that you can use
|
92
|
+
/ pass to the templates.
|
93
|
+
|
94
|
+
## License
|
95
|
+
|
96
|
+
This project is shared under the MIT license. See the attached [LICENSE][] file
|
97
|
+
for details.
|
98
|
+
|
99
|
+
[LICENSE]: ./LICENSE
|
data/bin/asset-manifest
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "json"
|
5
|
+
require "digest/sha1"
|
6
|
+
require "digest/sha2"
|
7
|
+
require "fileutils"
|
8
|
+
require "asset_manifest"
|
9
|
+
|
10
|
+
options = {}
|
11
|
+
OptionParser.new do |o|
|
12
|
+
o.banner = "Usage: #{File.basename($0)} [options] asset..."
|
13
|
+
|
14
|
+
o.on("-d", "--directory DIR", "Public directory") do |dir|
|
15
|
+
options[:dir] = dir
|
16
|
+
end
|
17
|
+
end.parse!(ARGV)
|
18
|
+
|
19
|
+
manifest = Hash.new { |h,k| h[k] = {} }
|
20
|
+
|
21
|
+
ARGV.each do |path|
|
22
|
+
key = path.sub(/^#{Regexp.escape(options[:dir])}/, "")
|
23
|
+
|
24
|
+
body = File.read(path)
|
25
|
+
manifest[key][:integrity] = "sha256-" + Array(Digest::SHA256.digest(body)).pack("m0")
|
26
|
+
manifest[key][:checksum] = Digest::SHA1.hexdigest(body)
|
27
|
+
|
28
|
+
asset = AssetManifest::Asset.new(path.sub(".min.", "."), {
|
29
|
+
manifest: manifest.dup,
|
30
|
+
minify: path.include?(".min."),
|
31
|
+
integrity: manifest[key][:integrity],
|
32
|
+
checksum: manifest[key][:checksum],
|
33
|
+
})
|
34
|
+
|
35
|
+
FileUtils.cp(File.join(Dir.pwd, path), asset.path)
|
36
|
+
end
|
37
|
+
|
38
|
+
puts JSON.dump(manifest)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative "../asset_manifest"
|
2
|
+
|
3
|
+
# Cuba plugin to simplify using AssetManifest. Gives you an entrypoint into the set of
|
4
|
+
# helpers to render different HTML tags.
|
5
|
+
#
|
6
|
+
# All you need to do is add this:
|
7
|
+
#
|
8
|
+
# Cuba.plugin AssetManifest::Cuba
|
9
|
+
#
|
10
|
+
# For example, in your template you could do something like this:
|
11
|
+
#
|
12
|
+
# <%= assets.stylesheet_tag("/css/main.css") %>
|
13
|
+
# <%= assets.script_tag("/js/main.js") %>
|
14
|
+
#
|
15
|
+
# Configuration
|
16
|
+
# -------------
|
17
|
+
#
|
18
|
+
# You can configure the following things:
|
19
|
+
#
|
20
|
+
# * `Cuba.settings[:asset_manifest][:public]`:
|
21
|
+
# The public directory where assets go. Defaults to `./public`.
|
22
|
+
#
|
23
|
+
# * `Cuba.settings[:asset_manifest][:manifest]`:
|
24
|
+
# Path to the asset manifest. Defaults to `{{ public }}/manifest.json`.
|
25
|
+
#
|
26
|
+
# * `Cuba.settings[:asset_manifest][:minified]`:
|
27
|
+
# Whether to append a `min` to the filename. If this is trueish, then a link
|
28
|
+
# to `foo.xyz` would turn to a link to `foo.min.xyz`. Defaults to `nil`.
|
29
|
+
#
|
30
|
+
# * `Cuba.settings[:asset_manifest][:asset_host]`:
|
31
|
+
# Host to serve assets from. Defaults to an empty String (same root as the
|
32
|
+
# Cuba app).
|
33
|
+
#
|
34
|
+
module AssetManifest::Cuba
|
35
|
+
def self.setup(app)
|
36
|
+
settings = app.settings[:asset_manifest] ||= {}
|
37
|
+
|
38
|
+
settings[:public] ||= "./public"
|
39
|
+
settings[:manifest] ||= File.join(settings[:public], "manifest.json")
|
40
|
+
settings[:minified] ||= nil
|
41
|
+
settings[:asset_host] ||= ""
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Helper proxy for your apps.
|
45
|
+
#
|
46
|
+
# Example:
|
47
|
+
#
|
48
|
+
# <%= assets.stylesheet_tag("/css/main.css") %>
|
49
|
+
#
|
50
|
+
def assets
|
51
|
+
@_assets ||= AssetManifest::Helpers.new(
|
52
|
+
JSON.parse(File.read(settings[:asset_manifest][:manifest])),
|
53
|
+
{
|
54
|
+
minify: settings[:asset_manifest][:minified],
|
55
|
+
host: settings[:asset_manifest][:asset_host],
|
56
|
+
}
|
57
|
+
)
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
require_relative "asset_manifest/version"
|
2
|
+
|
3
|
+
module AssetManifest
|
4
|
+
class Helpers
|
5
|
+
def initialize(manifest, asset_options = {})
|
6
|
+
@manifest = manifest
|
7
|
+
@asset_options = asset_options
|
8
|
+
end
|
9
|
+
|
10
|
+
# Public: Generate a <link> tag.
|
11
|
+
#
|
12
|
+
# path - The absolute path (for the browser) to the stylesheet.
|
13
|
+
# html - A Hash with HTML attribute mappings for this tag.
|
14
|
+
# **opts - Any keyword arguments will be forwarded to the initializer of
|
15
|
+
# AssetManifest::Asset to calculate the tag's attributes.
|
16
|
+
#
|
17
|
+
# Returns a String.
|
18
|
+
def link_tag(path, html: {}, **opts)
|
19
|
+
link = asset(path, opts)
|
20
|
+
attrs = attributes(html)
|
21
|
+
attrs.concat(sri(link))
|
22
|
+
%Q(<link href="#{link.url}" #{attrs.join(" ")}>)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Public: Generate a <link> tag to a stylesheet.
|
26
|
+
#
|
27
|
+
# path - The absolute path (for the browser) to the stylesheet.
|
28
|
+
# html - A Hash with HTML attribute mappings for this tag.
|
29
|
+
# **opts - Any keyword arguments will be forwarded to the initializer of
|
30
|
+
# AssetManifest::Asset to calculate the tag's attributes.
|
31
|
+
#
|
32
|
+
# Returns a String.
|
33
|
+
def stylesheet_tag(path, html: {}, **opts)
|
34
|
+
html.update(rel: "stylesheet")
|
35
|
+
link_tag(path, html: html, **opts)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Generate a <script> tag to a JS file.
|
39
|
+
#
|
40
|
+
# path - The absolute path (for the browser) to the script.
|
41
|
+
# html - A Hash with HTML attribute mappings for this tag.
|
42
|
+
# **opts - Any keyword arguments will be forwarded to the initializer of
|
43
|
+
# AssetManifest::Asset to calculate the tag's attributes.
|
44
|
+
#
|
45
|
+
# Returns a String.
|
46
|
+
def script_tag(path, html: {}, **opts)
|
47
|
+
script = asset(path, opts)
|
48
|
+
attrs = attributes(html)
|
49
|
+
attrs.concat(sri(script))
|
50
|
+
%Q(<script src="#{script.url}" #{attrs.join(" ")}></script>)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Generate an <img> tag.
|
54
|
+
#
|
55
|
+
# path - The absolute path (for the browser) to the image.
|
56
|
+
# html - A Hash with HTML attribute mappings for this tag.
|
57
|
+
# **opts - Any keyword arguments will be forwarded to the initializer of
|
58
|
+
# AssetManifest::Asset to calculate the tag's attributes.
|
59
|
+
#
|
60
|
+
# Returns a String.
|
61
|
+
def image_tag(path, html: {}, **opts)
|
62
|
+
image = asset(path, opts)
|
63
|
+
attrs = attributes(html)
|
64
|
+
%Q(<img src="#{image.url}" #{attrs.join(" ")}>)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Internal: Generate an attribute list from a Hash. If there's a `:data` key
|
68
|
+
# and it's itself a Hash, its keys will get a `data-` prefix.
|
69
|
+
#
|
70
|
+
# opts - A Hash of attribute => value mappings. If a value is the literal
|
71
|
+
# `true`, then we'll consider the attribute to be a Boolean
|
72
|
+
# attribute and output it without value (ie. `{ hidden: true }`
|
73
|
+
# would transform into `["hidden"]`, not `['hidden="true"']`.
|
74
|
+
# prefix - A prefix to add to attribute names. Defaults to an empty String.
|
75
|
+
#
|
76
|
+
# Returns an Array of 'attribute="value"' Strings.
|
77
|
+
def attributes(opts, prefix="")
|
78
|
+
attrs = []
|
79
|
+
|
80
|
+
if Hash === opts[:data]
|
81
|
+
attrs.concat(attributes(opts.delete(:data), "data-"))
|
82
|
+
end
|
83
|
+
|
84
|
+
attrs.concat(
|
85
|
+
opts.map { |attr, val| val == true ? attr : %Q(#{attr}="#{val}") }
|
86
|
+
)
|
87
|
+
|
88
|
+
attrs
|
89
|
+
end
|
90
|
+
|
91
|
+
# Internal: Returns an AssetManifest::Asset for the given path, using the default
|
92
|
+
# asset options and any other options passed in the `opts` Hash.
|
93
|
+
def asset(path, opts)
|
94
|
+
Asset.new(path, manifest: @manifest, **@asset_options.merge(opts))
|
95
|
+
end
|
96
|
+
|
97
|
+
# Internal: Generate the Sub-Resource Integrity attributes for the tags that
|
98
|
+
# require it.
|
99
|
+
#
|
100
|
+
# tag - An AssetManifest::Asset object.
|
101
|
+
#
|
102
|
+
# Returns an Array of attribute=value Strings.
|
103
|
+
def sri(asset)
|
104
|
+
attrs = [%Q(integrity="#{asset.integrity}")]
|
105
|
+
attrs << %Q(crossorigin="#{asset.cors}") if asset.cors?
|
106
|
+
attrs
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Asset
|
111
|
+
# Public: Returns a String with the SRI hash for this asset.
|
112
|
+
attr_reader :integrity
|
113
|
+
|
114
|
+
# Public: Returns a String with the value of the crossorigin attribute, or
|
115
|
+
# `nil` if the asset isn't served from a special asset host.
|
116
|
+
attr_reader :cors
|
117
|
+
|
118
|
+
def initialize(path, manifest:,
|
119
|
+
host: "",
|
120
|
+
minify: nil,
|
121
|
+
integrity: nil,
|
122
|
+
checksum: nil,
|
123
|
+
cors: "anonymous")
|
124
|
+
minify = (minify if minify) # Ensure `false` gets converted to `nil`
|
125
|
+
@path = base_path(path, minify)
|
126
|
+
@manifest = manifest[@path]
|
127
|
+
@integrity = integrity || @manifest.fetch("integrity")
|
128
|
+
@checksum = checksum || @manifest.fetch("checksum")
|
129
|
+
@minify = minify
|
130
|
+
@host = host
|
131
|
+
@cors = cors if cors?
|
132
|
+
end
|
133
|
+
|
134
|
+
# Public: Returns a String with the path to the asset from the host's root
|
135
|
+
# (i.e. without the asset host).
|
136
|
+
def path
|
137
|
+
@_path ||= begin
|
138
|
+
sep = @minify ? ".min." : "."
|
139
|
+
*asset, ext = @path.split(sep)
|
140
|
+
asset.last << "-#{@checksum}"
|
141
|
+
[*asset, ext].join(sep)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
# Public: Returns a String with the full URL of the asset (including the
|
146
|
+
# asset host, if any).
|
147
|
+
def url
|
148
|
+
@url ||= File.join(@host, path)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Public: Returns a Boolean representing whether we should set up a
|
152
|
+
# `crossorigin` attribute for this asset.
|
153
|
+
def cors?
|
154
|
+
@host != ""
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def base_path(path, minify)
|
160
|
+
minify = "min" if minify
|
161
|
+
*asset, ext = path.split(".")
|
162
|
+
[*asset, *minify, ext].join(".")
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: asset_manifest
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.pre.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nicolas Sanguinetti
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-03-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: cutest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.2'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.2'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rack
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.5'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.5'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: nokogiri
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.6'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.6'
|
55
|
+
description: AssetManifest provides utilities for generating SRI and cache-busting
|
56
|
+
hashes to your static assets during compilation.
|
57
|
+
email:
|
58
|
+
- contacto@nicolassanguinetti.info
|
59
|
+
executables:
|
60
|
+
- asset-manifest
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files: []
|
63
|
+
files:
|
64
|
+
- LICENSE
|
65
|
+
- README.md
|
66
|
+
- bin/asset-manifest
|
67
|
+
- lib/asset_manifest.rb
|
68
|
+
- lib/asset_manifest/cuba.rb
|
69
|
+
- lib/asset_manifest/version.rb
|
70
|
+
homepage: http://github.com/13floor/asset_manifest
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata: {}
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: 1.3.1
|
88
|
+
requirements: []
|
89
|
+
rubyforge_project:
|
90
|
+
rubygems_version: 2.4.5.1
|
91
|
+
signing_key:
|
92
|
+
specification_version: 4
|
93
|
+
summary: Utilities for serving your static assets.
|
94
|
+
test_files: []
|