assiette 0.1.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 +7 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +14 -0
- data/config/routes.rb +5 -0
- data/lib/assiette/engine.rb +17 -0
- data/lib/assiette/helpers.rb +40 -0
- data/lib/assiette/rewriter.rb +30 -0
- data/lib/assiette/server.rb +170 -0
- data/lib/assiette/version.rb +5 -0
- data/lib/assiette/version_tag.rb +33 -0
- data/lib/assiette.rb +11 -0
- data/lib/generators/assiette/install/install_generator.rb +17 -0
- data/lib/generators/assiette/install/templates/initializer.rb.tt +19 -0
- metadata +85 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 53dcdd2252625c13acb100e88de0f7a0bdefe7e09ddf560a4505b8134488129a
|
|
4
|
+
data.tar.gz: b564439804f77d5630efee16e9102ba40e10e9d5b29295dff081ea51db921b03
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0da513d9800eb7f4041a2d3ce754d30da6ea05276a2dd868c6d094868a82f37d4040485040463f8885c25cac2f12fdd2c193be1c94b82a9fec46654d36b45c07
|
|
7
|
+
data.tar.gz: fbbceda42225f3dadc09e885909f66d7711593a62e491a80583d1f8a8e856223ecb0e8f2fcf4e714ac42e4d082badba92af003ee4fd1e1438958e2d74887792b
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Julik Tarkhanov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rake/testtask"
|
|
5
|
+
|
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
|
7
|
+
t.libs << "test"
|
|
8
|
+
t.pattern = "test/**/*_test.rb"
|
|
9
|
+
t.verbose = false
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "standard/rake"
|
|
13
|
+
|
|
14
|
+
task default: %i[test standard]
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/engine"
|
|
4
|
+
|
|
5
|
+
module Assiette
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace Assiette
|
|
8
|
+
|
|
9
|
+
# Inject helpers into all ActionView contexts so host apps
|
|
10
|
+
# get assiette_asset_path() and assiette_modulepreload_tags() for free
|
|
11
|
+
initializer "assiette.helpers" do
|
|
12
|
+
ActiveSupport.on_load(:action_view) do
|
|
13
|
+
include Assiette::Helpers
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Assiette
|
|
4
|
+
module Helpers
|
|
5
|
+
# Returns the URL path to an asset served by Assiette, with a cache-busting
|
|
6
|
+
# version tag appended.
|
|
7
|
+
def assiette_asset_path(path)
|
|
8
|
+
entry = request.env["assiette.stack"]&.last
|
|
9
|
+
raise "No Assiette::Server in middleware stack" unless entry
|
|
10
|
+
entry[:server].absolute_asset_url_path(path, entry[:script_name])
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Returns the SRI integrity hash for an asset, computed over the served
|
|
14
|
+
# (rewritten) content. Returns nil if the file is not found.
|
|
15
|
+
def assiette_asset_integrity(path)
|
|
16
|
+
entry = request.env["assiette.stack"]&.last
|
|
17
|
+
raise "No Assiette::Server in middleware stack" unless entry
|
|
18
|
+
entry[:server].asset_integrity(path)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Generates a <link rel="stylesheet"> tag with SRI integrity.
|
|
22
|
+
def assiette_stylesheet_tag(path)
|
|
23
|
+
tag.link(rel: "stylesheet", href: assiette_asset_path(path),
|
|
24
|
+
integrity: assiette_asset_integrity(path), crossorigin: "anonymous")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Generates <link rel="modulepreload"> tags for all detected ES modules
|
|
28
|
+
# under the configured asset roots. Each tag includes an SRI integrity
|
|
29
|
+
# hash computed over the served (rewritten) content.
|
|
30
|
+
def assiette_modulepreload_tags
|
|
31
|
+
entry = request.env["assiette.stack"]&.last
|
|
32
|
+
raise "No Assiette::Server in middleware stack" unless entry
|
|
33
|
+
modules = entry[:server].js_modules
|
|
34
|
+
safe_join(modules.map { |mod|
|
|
35
|
+
tag.link(rel: "modulepreload", href: assiette_asset_path(mod[:path]),
|
|
36
|
+
integrity: mod[:integrity], crossorigin: "anonymous")
|
|
37
|
+
}, "\n")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Assiette
|
|
4
|
+
module Rewriter
|
|
5
|
+
# Matches quoted strings that look like relative/absolute JS import paths.
|
|
6
|
+
# Handles ./ ../ / but NOT protocol-relative //
|
|
7
|
+
# $1 = quote char, $2 = path including extension
|
|
8
|
+
JS_IMPORT_RE = /(["'])(\.{0,2}\/(?!\/)[^"']*\.(?:js|mjs|es))\1/
|
|
9
|
+
|
|
10
|
+
# Matches url() in CSS with relative/absolute paths.
|
|
11
|
+
# Handles url(./path), url("./path"), url('../path'), url(/path)
|
|
12
|
+
# but NOT url(data:...), url(https://...), url(//...)
|
|
13
|
+
# $1 = opening (quote or empty), $2 = path, $3 = closing (quote or empty)
|
|
14
|
+
CSS_URL_RE = /url\((\s*["']?)(\.{0,2}\/(?!\/)[^)"']*?)(\s*["']?\s*)\)/
|
|
15
|
+
|
|
16
|
+
module_function
|
|
17
|
+
|
|
18
|
+
def rewrite_js_imports(source, version_tag)
|
|
19
|
+
source.gsub(JS_IMPORT_RE) do
|
|
20
|
+
"#{$1}#{$2}?v=#{version_tag}#{$1}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def rewrite_css_urls(source, version_tag)
|
|
25
|
+
source.gsub(CSS_URL_RE) do
|
|
26
|
+
"url(#{$1}#{$2}?v=#{version_tag}#{$3})"
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
require "digest/sha2"
|
|
5
|
+
require "base64"
|
|
6
|
+
require "pathname"
|
|
7
|
+
|
|
8
|
+
module Assiette
|
|
9
|
+
class Server
|
|
10
|
+
CONTENT_TYPES = {
|
|
11
|
+
".js" => "application/javascript",
|
|
12
|
+
".mjs" => "application/javascript",
|
|
13
|
+
".css" => "text/css",
|
|
14
|
+
".svg" => "image/svg+xml",
|
|
15
|
+
".png" => "image/png",
|
|
16
|
+
".ico" => "image/x-icon"
|
|
17
|
+
}.freeze
|
|
18
|
+
|
|
19
|
+
JS_EXTENSIONS = %w[.js .mjs].to_set.freeze
|
|
20
|
+
|
|
21
|
+
CACHE_CONTROL = "public, max-age=432000, must-revalidate"
|
|
22
|
+
|
|
23
|
+
def initialize(app, root:, additional_directory_mappings: {})
|
|
24
|
+
@app = app
|
|
25
|
+
@mappings = build_mappings(root, additional_directory_mappings)
|
|
26
|
+
@integrity_cache = {}
|
|
27
|
+
@integrity_mutex = Mutex.new
|
|
28
|
+
@modules_cache = nil
|
|
29
|
+
@modules_mutex = Mutex.new
|
|
30
|
+
@modules_version = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def call(env)
|
|
34
|
+
stack = (env["assiette.stack"] ||= [])
|
|
35
|
+
stack << {server: self, script_name: env["SCRIPT_NAME"].to_s}
|
|
36
|
+
|
|
37
|
+
result = serve(env)
|
|
38
|
+
return result if result
|
|
39
|
+
|
|
40
|
+
@app.call(env)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Public API for helpers
|
|
44
|
+
def absolute_asset_url_path(path, script_name = "")
|
|
45
|
+
clean = path.sub(%r{\A/}, "")
|
|
46
|
+
return nil unless resolve_file(clean)
|
|
47
|
+
"#{script_name}/#{clean}?v=#{Assiette.version_tag}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def asset_integrity(path)
|
|
51
|
+
version_tag = Assiette.version_tag
|
|
52
|
+
@integrity_mutex.synchronize do
|
|
53
|
+
if @integrity_version != version_tag
|
|
54
|
+
@integrity_cache = {}
|
|
55
|
+
@integrity_version = version_tag
|
|
56
|
+
end
|
|
57
|
+
return @integrity_cache[path] if @integrity_cache.key?(path)
|
|
58
|
+
|
|
59
|
+
clean = path.sub(%r{\A/}, "")
|
|
60
|
+
@integrity_cache[path] = compute_integrity(clean, version_tag)
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def js_modules
|
|
65
|
+
version_tag = Assiette.version_tag
|
|
66
|
+
@modules_mutex.synchronize do
|
|
67
|
+
return @modules_cache if @modules_version == version_tag
|
|
68
|
+
|
|
69
|
+
@modules_cache = @mappings.flat_map { |prefix, root|
|
|
70
|
+
Dir[File.join(root, "**/*.{js,mjs}")].filter_map { |abs|
|
|
71
|
+
next unless File.foreach(abs).any? { |line| line.match?(/\A\s*(import|export)\s/) }
|
|
72
|
+
relative = Pathname.new(abs).relative_path_from(root).to_s
|
|
73
|
+
mod_path = "/#{"#{prefix}/" unless prefix.empty?}#{relative}".squeeze("/")
|
|
74
|
+
{path: mod_path, integrity: asset_integrity(mod_path)}
|
|
75
|
+
}
|
|
76
|
+
}.uniq { |m| m[:path] }.sort_by { |m| m[:path] }
|
|
77
|
+
|
|
78
|
+
@modules_version = version_tag
|
|
79
|
+
@modules_cache
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def build_mappings(root, additional_directory_mappings)
|
|
86
|
+
mappings = [["", Pathname.new(root).expand_path]]
|
|
87
|
+
additional_directory_mappings.each do |prefix, path|
|
|
88
|
+
clean_prefix = prefix.to_s.sub(%r{\A/}, "").chomp("/")
|
|
89
|
+
mappings << [clean_prefix, Pathname.new(path).expand_path]
|
|
90
|
+
end
|
|
91
|
+
mappings
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def serve(env)
|
|
95
|
+
return unless env["REQUEST_METHOD"] == "GET" || env["REQUEST_METHOD"] == "HEAD"
|
|
96
|
+
|
|
97
|
+
path_info = Rack::Utils.unescape_path(env["PATH_INFO"])
|
|
98
|
+
path_info = path_info.sub(%r{\A/}, "")
|
|
99
|
+
|
|
100
|
+
extension = File.extname(path_info)
|
|
101
|
+
content_type = CONTENT_TYPES[extension]
|
|
102
|
+
return unless content_type
|
|
103
|
+
|
|
104
|
+
file_path = resolve_file(path_info)
|
|
105
|
+
return unless file_path
|
|
106
|
+
|
|
107
|
+
raw_bytes = File.binread(file_path)
|
|
108
|
+
|
|
109
|
+
# ETag from raw bytes for stability
|
|
110
|
+
etag = %("#{Digest::SHA1.hexdigest(raw_bytes)}")
|
|
111
|
+
if env["HTTP_IF_NONE_MATCH"] == etag
|
|
112
|
+
return [304, {"etag" => etag, "cache-control" => CACHE_CONTROL}, []]
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
query = Rack::Utils.parse_query(env["QUERY_STRING"])
|
|
116
|
+
tag = query["v"].to_s.empty? ? Assiette.version_tag : query["v"]
|
|
117
|
+
|
|
118
|
+
body = if JS_EXTENSIONS.include?(extension)
|
|
119
|
+
Rewriter.rewrite_js_imports(raw_bytes, tag)
|
|
120
|
+
elsif extension == ".css"
|
|
121
|
+
Rewriter.rewrite_css_urls(raw_bytes, tag)
|
|
122
|
+
else
|
|
123
|
+
raw_bytes
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
headers = {
|
|
127
|
+
"content-type" => content_type,
|
|
128
|
+
"content-length" => body.bytesize.to_s,
|
|
129
|
+
"cache-control" => CACHE_CONTROL,
|
|
130
|
+
"etag" => etag
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
[200, headers, [body]]
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def resolve_file(path)
|
|
137
|
+
clean = path.sub(%r{\A/}, "")
|
|
138
|
+
@mappings.each do |prefix, root|
|
|
139
|
+
if prefix.empty?
|
|
140
|
+
relative = clean
|
|
141
|
+
elsif clean.start_with?(prefix + "/")
|
|
142
|
+
relative = clean[(prefix.length + 1)..]
|
|
143
|
+
elsif clean == prefix
|
|
144
|
+
next
|
|
145
|
+
else
|
|
146
|
+
next
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
abs = root.join(relative).cleanpath
|
|
150
|
+
next unless abs.to_s.start_with?(root.to_s + "/")
|
|
151
|
+
return abs if abs.exist? && abs.file?
|
|
152
|
+
end
|
|
153
|
+
nil
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def compute_integrity(clean, version_tag)
|
|
157
|
+
file_path = resolve_file(clean)
|
|
158
|
+
return nil unless file_path
|
|
159
|
+
|
|
160
|
+
raw = File.read(file_path)
|
|
161
|
+
ext = File.extname(clean)
|
|
162
|
+
served = case ext
|
|
163
|
+
when ".js", ".mjs" then Rewriter.rewrite_js_imports(raw, version_tag)
|
|
164
|
+
when ".css" then Rewriter.rewrite_css_urls(raw, version_tag)
|
|
165
|
+
else raw
|
|
166
|
+
end
|
|
167
|
+
"sha256-#{Base64.strict_encode64(Digest::SHA256.digest(served))}"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest/sha1"
|
|
4
|
+
|
|
5
|
+
module Assiette
|
|
6
|
+
# Computes a short version tag for cache busting.
|
|
7
|
+
# In development: timestamp for instant invalidation on each request
|
|
8
|
+
# In production: derived from APP_REVISION env var or Gemfile.lock digest
|
|
9
|
+
def self.version_tag
|
|
10
|
+
@version_tag ||= compute_version_tag
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.reset_version_tag!
|
|
14
|
+
@version_tag = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.compute_version_tag
|
|
18
|
+
if Rails.env.development?
|
|
19
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
20
|
+
elsif (app_revision = ENV["APP_REVISION"]).present?
|
|
21
|
+
Digest::SHA1.hexdigest(app_revision)[0, 4]
|
|
22
|
+
else
|
|
23
|
+
gemfile_lock_path = Rails.root.join("Gemfile.lock")
|
|
24
|
+
if gemfile_lock_path.exist?
|
|
25
|
+
Digest::SHA1.file(gemfile_lock_path).hexdigest[0, 4]
|
|
26
|
+
else
|
|
27
|
+
(Time.now.utc.to_i / 300).to_s(16)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private_class_method :compute_version_tag
|
|
33
|
+
end
|
data/lib/assiette.rb
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "assiette/version"
|
|
4
|
+
require_relative "assiette/version_tag"
|
|
5
|
+
require_relative "assiette/rewriter"
|
|
6
|
+
require_relative "assiette/server"
|
|
7
|
+
require_relative "assiette/helpers"
|
|
8
|
+
require_relative "assiette/engine"
|
|
9
|
+
|
|
10
|
+
module Assiette
|
|
11
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Assiette
|
|
6
|
+
module Generators
|
|
7
|
+
class InstallGenerator < Rails::Generators::Base
|
|
8
|
+
source_root File.expand_path("templates", __dir__)
|
|
9
|
+
|
|
10
|
+
desc "Creates an Assiette initializer with middleware configuration"
|
|
11
|
+
|
|
12
|
+
def create_initializer
|
|
13
|
+
template "initializer.rb.tt", "config/initializers/assiette.rb"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Assiette serves static assets (JS, CSS, SVG, etc.) from your app with
|
|
4
|
+
# cache headers, SRI integrity hashes, and automatic JS import rewriting.
|
|
5
|
+
#
|
|
6
|
+
# This file runs during :load_config_initializers, which is before the
|
|
7
|
+
# middleware stack is frozen (:build_middleware_stack) in Rails 8.1+.
|
|
8
|
+
|
|
9
|
+
# Serve files from app/assets (JS modules, CSS, SVGs, etc.)
|
|
10
|
+
Rails.application.config.middleware.use(
|
|
11
|
+
Assiette::Server,
|
|
12
|
+
root: Rails.root.join("app/assets")
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Serve files from public/ (favicons, static images, etc.)
|
|
16
|
+
Rails.application.config.middleware.use(
|
|
17
|
+
Assiette::Server,
|
|
18
|
+
root: Rails.root.join("public")
|
|
19
|
+
)
|
metadata
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: assiette
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Julik Tarkhanov
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 2026-05-20 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: railties
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '7.2'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '7.2'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: actionpack
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '7.2'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '7.2'
|
|
40
|
+
description: Serves static assets with cache-busting version tags and on-the-fly JS/CSS
|
|
41
|
+
URL rewriting, without requiring any asset pipeline
|
|
42
|
+
email:
|
|
43
|
+
- me@julik.nl
|
|
44
|
+
executables: []
|
|
45
|
+
extensions: []
|
|
46
|
+
extra_rdoc_files: []
|
|
47
|
+
files:
|
|
48
|
+
- LICENSE.txt
|
|
49
|
+
- Rakefile
|
|
50
|
+
- config/routes.rb
|
|
51
|
+
- lib/assiette.rb
|
|
52
|
+
- lib/assiette/engine.rb
|
|
53
|
+
- lib/assiette/helpers.rb
|
|
54
|
+
- lib/assiette/rewriter.rb
|
|
55
|
+
- lib/assiette/server.rb
|
|
56
|
+
- lib/assiette/version.rb
|
|
57
|
+
- lib/assiette/version_tag.rb
|
|
58
|
+
- lib/generators/assiette/install/install_generator.rb
|
|
59
|
+
- lib/generators/assiette/install/templates/initializer.rb.tt
|
|
60
|
+
homepage: https://github.com/julik/assiette
|
|
61
|
+
licenses:
|
|
62
|
+
- MIT
|
|
63
|
+
metadata:
|
|
64
|
+
allowed_push_host: https://rubygems.org
|
|
65
|
+
homepage_uri: https://github.com/julik/assiette
|
|
66
|
+
source_code_uri: https://github.com/julik/assiette
|
|
67
|
+
changelog_uri: https://github.com/julik/assiette/blob/main/CHANGELOG.md
|
|
68
|
+
rdoc_options: []
|
|
69
|
+
require_paths:
|
|
70
|
+
- lib
|
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - ">="
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 3.1.0
|
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
|
+
requirements:
|
|
78
|
+
- - ">="
|
|
79
|
+
- !ruby/object:Gem::Version
|
|
80
|
+
version: '0'
|
|
81
|
+
requirements: []
|
|
82
|
+
rubygems_version: 3.6.6
|
|
83
|
+
specification_version: 4
|
|
84
|
+
summary: Zero-build asset serving for Rails engines
|
|
85
|
+
test_files: []
|