sprockets 2.3.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of sprockets might be problematic. Click here for more details.

Files changed (84) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +2 -2
  3. data/README.md +332 -115
  4. data/bin/sprockets +8 -0
  5. data/lib/rake/sprocketstask.rb +25 -13
  6. data/lib/sprockets/asset.rb +143 -205
  7. data/lib/sprockets/autoload/closure.rb +7 -0
  8. data/lib/sprockets/autoload/coffee_script.rb +7 -0
  9. data/lib/sprockets/autoload/eco.rb +7 -0
  10. data/lib/sprockets/autoload/ejs.rb +7 -0
  11. data/lib/sprockets/autoload/sass.rb +7 -0
  12. data/lib/sprockets/autoload/uglifier.rb +7 -0
  13. data/lib/sprockets/autoload/yui.rb +7 -0
  14. data/lib/sprockets/autoload.rb +11 -0
  15. data/lib/sprockets/base.rb +49 -257
  16. data/lib/sprockets/bower.rb +58 -0
  17. data/lib/sprockets/bundle.rb +65 -0
  18. data/lib/sprockets/cache/file_store.rb +165 -14
  19. data/lib/sprockets/cache/memory_store.rb +66 -0
  20. data/lib/sprockets/cache/null_store.rb +46 -0
  21. data/lib/sprockets/cache.rb +234 -0
  22. data/lib/sprockets/cached_environment.rb +69 -0
  23. data/lib/sprockets/closure_compressor.rb +53 -0
  24. data/lib/sprockets/coffee_script_processor.rb +25 -0
  25. data/lib/sprockets/coffee_script_template.rb +6 -0
  26. data/lib/sprockets/compressing.rb +74 -0
  27. data/lib/sprockets/configuration.rb +83 -0
  28. data/lib/sprockets/context.rb +125 -131
  29. data/lib/sprockets/dependencies.rb +73 -0
  30. data/lib/sprockets/digest_utils.rb +156 -0
  31. data/lib/sprockets/directive_processor.rb +209 -211
  32. data/lib/sprockets/eco_processor.rb +32 -0
  33. data/lib/sprockets/eco_template.rb +3 -35
  34. data/lib/sprockets/ejs_processor.rb +31 -0
  35. data/lib/sprockets/ejs_template.rb +3 -34
  36. data/lib/sprockets/encoding_utils.rb +258 -0
  37. data/lib/sprockets/engines.rb +45 -38
  38. data/lib/sprockets/environment.rb +17 -67
  39. data/lib/sprockets/erb_processor.rb +30 -0
  40. data/lib/sprockets/erb_template.rb +6 -0
  41. data/lib/sprockets/errors.rb +6 -13
  42. data/lib/sprockets/file_reader.rb +15 -0
  43. data/lib/sprockets/http_utils.rb +115 -0
  44. data/lib/sprockets/jst_processor.rb +35 -19
  45. data/lib/sprockets/legacy.rb +314 -0
  46. data/lib/sprockets/legacy_proc_processor.rb +35 -0
  47. data/lib/sprockets/legacy_tilt_processor.rb +29 -0
  48. data/lib/sprockets/loader.rb +176 -0
  49. data/lib/sprockets/manifest.rb +179 -98
  50. data/lib/sprockets/manifest_utils.rb +45 -0
  51. data/lib/sprockets/mime.rb +114 -32
  52. data/lib/sprockets/path_dependency_utils.rb +85 -0
  53. data/lib/sprockets/path_digest_utils.rb +47 -0
  54. data/lib/sprockets/path_utils.rb +282 -0
  55. data/lib/sprockets/paths.rb +81 -0
  56. data/lib/sprockets/processing.rb +157 -189
  57. data/lib/sprockets/processor_utils.rb +103 -0
  58. data/lib/sprockets/resolve.rb +208 -0
  59. data/lib/sprockets/sass_cache_store.rb +19 -15
  60. data/lib/sprockets/sass_compressor.rb +59 -0
  61. data/lib/sprockets/sass_functions.rb +2 -0
  62. data/lib/sprockets/sass_importer.rb +2 -29
  63. data/lib/sprockets/sass_processor.rb +285 -0
  64. data/lib/sprockets/sass_template.rb +4 -44
  65. data/lib/sprockets/server.rb +109 -84
  66. data/lib/sprockets/transformers.rb +145 -0
  67. data/lib/sprockets/uglifier_compressor.rb +63 -0
  68. data/lib/sprockets/uri_utils.rb +190 -0
  69. data/lib/sprockets/utils.rb +193 -44
  70. data/lib/sprockets/version.rb +1 -1
  71. data/lib/sprockets/yui_compressor.rb +65 -0
  72. data/lib/sprockets.rb +144 -53
  73. metadata +248 -238
  74. data/lib/sprockets/asset_attributes.rb +0 -126
  75. data/lib/sprockets/bundled_asset.rb +0 -79
  76. data/lib/sprockets/caching.rb +0 -96
  77. data/lib/sprockets/charset_normalizer.rb +0 -41
  78. data/lib/sprockets/index.rb +0 -99
  79. data/lib/sprockets/processed_asset.rb +0 -152
  80. data/lib/sprockets/processor.rb +0 -32
  81. data/lib/sprockets/safety_colons.rb +0 -28
  82. data/lib/sprockets/scss_template.rb +0 -13
  83. data/lib/sprockets/static_asset.rb +0 -57
  84. data/lib/sprockets/trail.rb +0 -90
@@ -0,0 +1,176 @@
1
+ require 'sprockets/asset'
2
+ require 'sprockets/digest_utils'
3
+ require 'sprockets/engines'
4
+ require 'sprockets/errors'
5
+ require 'sprockets/file_reader'
6
+ require 'sprockets/mime'
7
+ require 'sprockets/path_utils'
8
+ require 'sprockets/processing'
9
+ require 'sprockets/processor_utils'
10
+ require 'sprockets/resolve'
11
+ require 'sprockets/transformers'
12
+ require 'sprockets/uri_utils'
13
+
14
+ module Sprockets
15
+ # The loader phase takes a asset URI location and returns a constructed Asset
16
+ # object.
17
+ module Loader
18
+ include DigestUtils, PathUtils, ProcessorUtils, URIUtils
19
+ include Engines, Mime, Processing, Resolve, Transformers
20
+
21
+ # Public: Load Asset by AssetURI.
22
+ #
23
+ # uri - AssetURI
24
+ #
25
+ # Returns Asset.
26
+ def load(uri)
27
+ filename, params = parse_asset_uri(uri)
28
+ if params.key?(:id)
29
+ asset = cache.fetch("asset-uri:#{VERSION}#{uri}") do
30
+ load_asset_by_id_uri(uri, filename, params)
31
+ end
32
+ else
33
+ asset = fetch_asset_from_dependency_cache(uri, filename) do |paths|
34
+ if paths
35
+ digest = digest(resolve_dependencies(paths))
36
+ if id_uri = cache.get("asset-uri-digest:#{VERSION}:#{uri}:#{digest}", true)
37
+ cache.get("asset-uri:#{VERSION}:#{id_uri}", true)
38
+ end
39
+ else
40
+ load_asset_by_uri(uri, filename, params)
41
+ end
42
+ end
43
+ end
44
+ Asset.new(self, asset)
45
+ end
46
+
47
+ private
48
+ def load_asset_by_id_uri(uri, filename, params)
49
+ # Internal assertion, should be routed through load_asset_by_uri
50
+ unless id = params.delete(:id)
51
+ raise ArgumentError, "expected uri to have an id: #{uri}"
52
+ end
53
+
54
+ uri = build_asset_uri(filename, params)
55
+ asset = load_asset_by_uri(uri, filename, params)
56
+
57
+ if id && asset[:id] != id
58
+ raise VersionNotFound, "could not find specified id: #{uri}##{id}"
59
+ end
60
+
61
+ asset
62
+ end
63
+
64
+ def load_asset_by_uri(uri, filename, params)
65
+ # Internal assertion, should be routed through load_asset_by_id_uri
66
+ if params.key?(:id)
67
+ raise ArgumentError, "expected uri to have no id: #{uri}"
68
+ end
69
+
70
+ unless file?(filename)
71
+ raise FileNotFound, "could not find file: #{filename}"
72
+ end
73
+
74
+ load_path, logical_path = paths_split(config[:paths], filename)
75
+
76
+ unless load_path
77
+ raise FileOutsidePaths, "#{filename} is no longer under a load path: #{self.paths.join(', ')}"
78
+ end
79
+
80
+ logical_path, file_type, engine_extnames, _ = parse_path_extnames(logical_path)
81
+ logical_path = normalize_logical_path(logical_path)
82
+ name = logical_path
83
+
84
+ if pipeline = params[:pipeline]
85
+ logical_path += ".#{pipeline}"
86
+ end
87
+
88
+ if type = params[:type]
89
+ logical_path += config[:mime_types][type][:extensions].first
90
+ end
91
+
92
+ if type != file_type && !config[:transformers][file_type][type]
93
+ raise ConversionError, "could not convert #{file_type.inspect} to #{type.inspect}"
94
+ end
95
+
96
+ processors = processors_for(type, file_type, engine_extnames, pipeline)
97
+
98
+ processors_dep_uri = build_processors_uri(type, file_type, engine_extnames, pipeline)
99
+ dependencies = config[:dependencies] + [processors_dep_uri]
100
+
101
+ # Read into memory and process if theres a processor pipeline
102
+ if processors.any?
103
+ result = call_processors(processors, {
104
+ environment: self,
105
+ cache: self.cache,
106
+ uri: uri,
107
+ filename: filename,
108
+ load_path: load_path,
109
+ name: name,
110
+ content_type: type,
111
+ metadata: { dependencies: dependencies }
112
+ })
113
+ source = result.delete(:data)
114
+ metadata = result.merge!(
115
+ charset: source.encoding.name.downcase,
116
+ digest: digest(source),
117
+ length: source.bytesize
118
+ )
119
+ else
120
+ metadata = {
121
+ digest: file_digest(filename),
122
+ length: self.stat(filename).size,
123
+ dependencies: dependencies
124
+ }
125
+ end
126
+
127
+ asset = {
128
+ uri: uri,
129
+ load_path: load_path,
130
+ filename: filename,
131
+ name: name,
132
+ logical_path: logical_path,
133
+ content_type: type,
134
+ source: source,
135
+ metadata: metadata,
136
+ integrity: integrity_uri(metadata[:digest], type),
137
+ dependencies_digest: digest(resolve_dependencies(metadata[:dependencies]))
138
+ }
139
+
140
+ asset[:id] = pack_hexdigest(digest(asset))
141
+ asset[:uri] = build_asset_uri(filename, params.merge(id: asset[:id]))
142
+
143
+ # Deprecated: Avoid tracking Asset mtime
144
+ asset[:mtime] = metadata[:dependencies].map { |u|
145
+ if u.start_with?("file-digest:")
146
+ s = self.stat(parse_file_digest_uri(u))
147
+ s ? s.mtime.to_i : 0
148
+ else
149
+ 0
150
+ end
151
+ }.max
152
+
153
+ cache.set("asset-uri:#{VERSION}:#{asset[:uri]}", asset, true)
154
+ cache.set("asset-uri-digest:#{VERSION}:#{uri}:#{asset[:dependencies_digest]}", asset[:uri], true)
155
+
156
+ asset
157
+ end
158
+
159
+ def fetch_asset_from_dependency_cache(uri, filename, limit = 3)
160
+ key = "asset-uri-cache-dependencies:#{VERSION}:#{uri}:#{file_digest(filename)}"
161
+ history = cache.get(key) || []
162
+
163
+ history.each_with_index do |deps, index|
164
+ if asset = yield(deps)
165
+ cache.set(key, history.rotate!(index)) if index > 0
166
+ return asset
167
+ end
168
+ end
169
+
170
+ asset = yield
171
+ deps = asset[:metadata][:dependencies]
172
+ cache.set(key, history.unshift(deps).take(limit))
173
+ asset
174
+ end
175
+ end
176
+ end
@@ -1,51 +1,87 @@
1
- require 'multi_json'
1
+ require 'json'
2
2
  require 'time'
3
+ require 'sprockets/manifest_utils'
3
4
 
4
5
  module Sprockets
5
- # The Manifest logs the contents of assets compiled to a single
6
- # directory. It records basic attributes about the asset for fast
7
- # lookup without having to compile. A pointer from each logical path
8
- # indicates with fingerprinted asset is the current one.
6
+ # The Manifest logs the contents of assets compiled to a single directory. It
7
+ # records basic attributes about the asset for fast lookup without having to
8
+ # compile. A pointer from each logical path indicates which fingerprinted
9
+ # asset is the current one.
9
10
  #
10
- # The JSON is part of the public API and should be considered
11
- # stable. This should make it easy to read from other programming
12
- # languages and processes that don't have sprockets loaded. See
13
- # `#assets` and `#files` for more infomation about the structure.
11
+ # The JSON is part of the public API and should be considered stable. This
12
+ # should make it easy to read from other programming languages and processes
13
+ # that don't have sprockets loaded. See `#assets` and `#files` for more
14
+ # infomation about the structure.
14
15
  class Manifest
15
- attr_reader :environment, :path, :dir
16
+ include ManifestUtils
16
17
 
17
- # Create new Manifest associated with an `environment`. `path` is
18
- # a full path to the manifest json file. The file may or may not
19
- # already exist. The dirname of the `path` will be used to write
20
- # compiled assets to. Otherwise, if the path is a directory, the
21
- # filename will default to "manifest.json" in that directory.
18
+ attr_reader :environment
19
+
20
+ # Create new Manifest associated with an `environment`. `filename` is a full
21
+ # path to the manifest json file. The file may or may not already exist. The
22
+ # dirname of the `filename` will be used to write compiled assets to.
23
+ # Otherwise, if the path is a directory, the filename will default a random
24
+ # ".sprockets-manifest-*.json" file in that directory.
22
25
  #
23
26
  # Manifest.new(environment, "./public/assets/manifest.json")
24
27
  #
25
- def initialize(environment, path)
26
- @environment = environment
28
+ def initialize(*args)
29
+ if args.first.is_a?(Base) || args.first.nil?
30
+ @environment = args.shift
31
+ end
32
+
33
+ @directory, @filename = args[0], args[1]
34
+
35
+ # Whether the manifest file is using the old manifest-*.json naming convention
36
+ @legacy_manifest = false
37
+
38
+ # Expand paths
39
+ @directory = File.expand_path(@directory) if @directory
40
+ @filename = File.expand_path(@filename) if @filename
41
+
42
+ # If filename is given as the second arg
43
+ if @directory && File.extname(@directory) != ""
44
+ @directory, @filename = nil, @directory
45
+ end
27
46
 
28
- if File.extname(path) == ""
29
- @dir = File.expand_path(path)
30
- @path = File.join(@dir, 'manifest.json')
31
- else
32
- @path = File.expand_path(path)
33
- @dir = File.dirname(path)
47
+ # Default dir to the directory of the filename
48
+ @directory ||= File.dirname(@filename) if @filename
49
+
50
+ # If directory is given w/o filename, pick a random manifest location
51
+ @rename_filename = nil
52
+ if @directory && @filename.nil?
53
+ @filename = find_directory_manifest(@directory)
54
+
55
+ # If legacy manifest name autodetected, mark to rename on save
56
+ if File.basename(@filename).start_with?("manifest")
57
+ @rename_filename = File.join(@directory, generate_manifest_path)
58
+ end
34
59
  end
35
60
 
36
- data = nil
61
+ unless @directory && @filename
62
+ raise ArgumentError, "manifest requires output filename"
63
+ end
64
+
65
+ data = {}
37
66
 
38
67
  begin
39
- if File.exist?(@path)
40
- data = MultiJson.decode(File.read(@path))
68
+ if File.exist?(@filename)
69
+ data = json_decode(File.read(@filename))
41
70
  end
42
- rescue MultiJson::DecodeError => e
43
- logger.error "#{@path} is invalid: #{e.class} #{e.message}"
71
+ rescue JSON::ParserError => e
72
+ logger.error "#{@filename} is invalid: #{e.class} #{e.message}"
44
73
  end
45
74
 
46
- @data = data.is_a?(Hash) ? data : {}
75
+ @data = data
47
76
  end
48
77
 
78
+ # Returns String path to manifest.json file.
79
+ attr_reader :filename
80
+ alias_method :path, :filename
81
+
82
+ attr_reader :directory
83
+ alias_method :dir, :directory
84
+
49
85
  # Returns internal assets mapping. Keys are logical paths which
50
86
  # map to the latest fingerprinted filename.
51
87
  #
@@ -75,6 +111,40 @@ module Sprockets
75
111
  @data['files'] ||= {}
76
112
  end
77
113
 
114
+ # Public: Find all assets matching pattern set in environment.
115
+ #
116
+ # Returns Enumerator of Assets.
117
+ def find(*args)
118
+ unless environment
119
+ raise Error, "manifest requires environment for compilation"
120
+ end
121
+
122
+ return to_enum(__method__, *args) unless block_given?
123
+
124
+ paths, filters = args.flatten.partition { |arg| self.class.simple_logical_path?(arg) }
125
+ filters = filters.map { |arg| self.class.compile_match_filter(arg) }
126
+
127
+ environment = self.environment.cached
128
+
129
+ paths.each do |path|
130
+ environment.find_all_linked_assets(path) do |asset|
131
+ yield asset
132
+ end
133
+ end
134
+
135
+ if filters.any?
136
+ environment.logical_paths do |logical_path, filename|
137
+ if filters.any? { |f| f.call(logical_path, filename) }
138
+ environment.find_all_linked_assets(filename) do |asset|
139
+ yield asset
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ nil
146
+ end
147
+
78
148
  # Compile and write asset to directory. The asset is written to a
79
149
  # fingerprinted filename like
80
150
  # `application-2e8e9a7c6b0aafa0c9bdeec90ea30213.js`. An entry is
@@ -83,32 +153,36 @@ module Sprockets
83
153
  # compile("application.js")
84
154
  #
85
155
  def compile(*args)
86
- paths = environment.each_logical_path(*args).to_a +
87
- args.flatten.select { |fn| Pathname.new(fn).absolute? }
156
+ unless environment
157
+ raise Error, "manifest requires environment for compilation"
158
+ end
88
159
 
89
- paths.each do |path|
90
- if asset = find_asset(path)
91
- files[asset.digest_path] = {
92
- 'logical_path' => asset.logical_path,
93
- 'mtime' => asset.mtime.iso8601,
94
- 'size' => asset.bytesize,
95
- 'digest' => asset.digest
96
- }
97
- assets[asset.logical_path] = asset.digest_path
98
-
99
- target = File.join(dir, asset.digest_path)
100
-
101
- if File.exist?(target)
102
- logger.debug "Skipping #{target}, already exists"
103
- else
104
- logger.info "Writing #{target}"
105
- asset.write_to target
106
- end
160
+ filenames = []
161
+
162
+ find(*args) do |asset|
163
+ files[asset.digest_path] = {
164
+ 'logical_path' => asset.logical_path,
165
+ 'mtime' => asset.mtime.iso8601,
166
+ 'size' => asset.bytesize,
167
+ 'digest' => asset.hexdigest,
168
+ 'integrity' => asset.integrity
169
+ }
170
+ assets[asset.logical_path] = asset.digest_path
107
171
 
108
- save
109
- asset
172
+ target = File.join(dir, asset.digest_path)
173
+
174
+ if File.exist?(target)
175
+ logger.debug "Skipping #{target}, already exists"
176
+ else
177
+ logger.info "Writing #{target}"
178
+ asset.write_to target
110
179
  end
180
+
181
+ filenames << asset.filename
111
182
  end
183
+ save
184
+
185
+ filenames
112
186
  end
113
187
 
114
188
  # Removes file from directory and from manifest. `filename` must
@@ -129,76 +203,83 @@ module Sprockets
129
203
 
130
204
  save
131
205
 
132
- logger.warn "Removed #{filename}"
206
+ logger.info "Removed #{filename}"
133
207
 
134
208
  nil
135
209
  end
136
210
 
137
211
  # Cleanup old assets in the compile directory. By default it will
138
- # keep the latest version plus 2 backups.
139
- def clean(keep = 2)
140
- self.assets.keys.each do |logical_path|
141
- # Get assets sorted by ctime, newest first
142
- assets = backups_for(logical_path)
212
+ # keep the latest version, 2 backups and any created within the past hour.
213
+ #
214
+ # Examples
215
+ #
216
+ # To force only 1 backup to be kept, set count=1 and age=0.
217
+ #
218
+ # To only keep files created within the last 10 minutes, set count=0 and
219
+ # age=600.
220
+ #
221
+ def clean(count = 2, age = 3600)
222
+ asset_versions = files.group_by { |_, attrs| attrs['logical_path'] }
143
223
 
144
- # Keep the last N backups
145
- assets = assets[keep..-1] || []
224
+ asset_versions.each do |logical_path, versions|
225
+ current = assets[logical_path]
146
226
 
147
- # Remove old assets
148
- assets.each { |path, _| remove(path) }
227
+ versions.reject { |path, _|
228
+ path == current
229
+ }.sort_by { |_, attrs|
230
+ # Sort by timestamp
231
+ Time.parse(attrs['mtime'])
232
+ }.reverse.each_with_index.drop_while { |(_, attrs), index|
233
+ age = [0, Time.now - Time.parse(attrs['mtime'])].max
234
+ # Keep if under age or within the count limit
235
+ age < age || index < count
236
+ }.each { |(path, _), _|
237
+ # Remove old assets
238
+ remove(path)
239
+ }
149
240
  end
150
241
  end
151
242
 
152
243
  # Wipe directive
153
244
  def clobber
154
- FileUtils.rm_r(@dir) if File.exist?(@dir)
155
- logger.warn "Removed #{@dir}"
245
+ FileUtils.rm_r(directory) if File.exist?(directory)
246
+ logger.info "Removed #{directory}"
156
247
  nil
157
248
  end
158
249
 
159
- protected
160
- # Finds all the backup assets for a logical path. The latest
161
- # version is always excluded. The return array is sorted by the
162
- # assets mtime in descending order (Newest to oldest).
163
- def backups_for(logical_path)
164
- files.select { |filename, attrs|
165
- # Matching logical paths
166
- attrs['logical_path'] == logical_path &&
167
- # Excluding whatever asset is the current
168
- assets[logical_path] != filename
169
- }.sort_by { |filename, attrs|
170
- # Sort by timestamp
171
- Time.parse(attrs['mtime'])
172
- }.reverse
250
+ # Persist manfiest back to FS
251
+ def save
252
+ if @rename_filename
253
+ logger.info "Renaming #{@filename} to #{@rename_filename}"
254
+ FileUtils.mv(@filename, @rename_filename)
255
+ @filename = @rename_filename
256
+ @rename_filename = nil
173
257
  end
174
258
 
175
- # Basic wrapper around Environment#find_asset. Logs compile time.
176
- def find_asset(logical_path)
177
- asset = nil
178
- ms = benchmark do
179
- asset = environment.find_asset(logical_path)
180
- end
181
- logger.warn "Compiled #{logical_path} (#{ms}ms)"
182
- asset
259
+ data = json_encode(@data)
260
+ FileUtils.mkdir_p File.dirname(@filename)
261
+ PathUtils.atomic_write(@filename) do |f|
262
+ f.write(data)
183
263
  end
264
+ end
184
265
 
185
- # Persist manfiest back to FS
186
- def save
187
- FileUtils.mkdir_p dir
188
- File.open(path, 'w') do |f|
189
- f.write MultiJson.encode(@data)
190
- end
266
+ private
267
+ def json_decode(obj)
268
+ JSON.parse(obj, create_additions: false)
191
269
  end
192
270
 
193
- private
194
- def logger
195
- environment.logger
271
+ def json_encode(obj)
272
+ JSON.generate(obj)
196
273
  end
197
274
 
198
- def benchmark
199
- start_time = Time.now.to_f
200
- yield
201
- ((Time.now.to_f - start_time) * 1000).to_i
275
+ def logger
276
+ if environment
277
+ environment.logger
278
+ else
279
+ logger = Logger.new($stderr)
280
+ logger.level = Logger::FATAL
281
+ logger
282
+ end
202
283
  end
203
284
  end
204
285
  end
@@ -0,0 +1,45 @@
1
+ require 'securerandom'
2
+
3
+ module Sprockets
4
+ # Public: Manifest utilities.
5
+ module ManifestUtils
6
+ extend self
7
+
8
+ MANIFEST_RE = /^\.sprockets-manifest-[0-9a-f]{32}.json$/
9
+ LEGACY_MANIFEST_RE = /^manifest(-[0-9a-f]{32})?.json$/
10
+
11
+ # Public: Generate a new random manifest path.
12
+ #
13
+ # Manifests are not intended to be accessed publicly, but typically live
14
+ # alongside public assets for convenience. To avoid being served, the
15
+ # filename is prefixed with a "." which is usually hidden by web servers
16
+ # like Apache. To help in other environments that may not control this,
17
+ # a random hex string is appended to the filename to prevent people from
18
+ # guessing the location. If directory indexes are enabled on the server,
19
+ # all bets are off.
20
+ #
21
+ # Return String path.
22
+ def generate_manifest_path
23
+ ".sprockets-manifest-#{SecureRandom.hex(16)}.json"
24
+ end
25
+
26
+ # Public: Find or pick a new manifest filename for target build directory.
27
+ #
28
+ # dirname - String dirname
29
+ #
30
+ # Examples
31
+ #
32
+ # find_directory_manifest("/app/public/assets")
33
+ # # => "/app/public/assets/.sprockets-manifest-abc123.json"
34
+ #
35
+ # Returns String filename.
36
+ def find_directory_manifest(dirname)
37
+ entries = File.directory?(dirname) ? Dir.entries(dirname) : []
38
+ entry = entries.find { |e| e =~ MANIFEST_RE } ||
39
+ # Deprecated: Will be removed in 4.x
40
+ entries.find { |e| e =~ LEGACY_MANIFEST_RE } ||
41
+ generate_manifest_path
42
+ File.join(dirname, entry)
43
+ end
44
+ end
45
+ end