# frozen_string_literal: true require "fileutils" require "json" require "hanami/assets/bundler/compressor" require "hanami/assets/bundler/asset" require "hanami/assets/bundler/manifest_entry" module Hanami module Assets # Bundle assets from a single application. # # @since 0.1.0 # @api private class Bundler # @since 0.1.0 # @api private DEFAULT_PERMISSIONS = 0o644 # @since 0.1.0 # @api private URL_SEPARATOR = "/" # @since 0.1.0 # @api private URL_REPLACEMENT = "" # Return a new instance # # @param configuration [Hanami::Assets::Configuration] a single application configuration # # @param duplicates [Array] the duplicated frameworks # (one for each application) # # @return [Hanami::Assets::Bundler] a new instance # # @since 0.1.0 # @api private def initialize(configuration, duplicates) @manifest = Hash[] @configuration = configuration @duplicates = duplicates end # Start the process. # # For each asset contained in the sources and third party gems, it will: # # * Compress # * Create a fingerprinted version of the file # * Generate a subresource integrity digest # # At the end it will generate a manifest # # @see Hanami::Assets::Configuration#fingerprint # @see Hanami::Assets::Configuration#manifest # @see Hanami::Assets::Configuration#manifest_path def run assets.each do |path| unless File.directory?(path) configuration = _configuration_for(path) process(Asset.new(path, configuration)) end end write_manifest_file end private # @since 0.3.0 # @api private def process(asset) compress_in_place!(asset) copy_to_fingerprinted_location!(asset) @manifest.merge!(ManifestEntry.new(asset).entry) end # @since 0.3.0 # @api private def copy_to_fingerprinted_location!(asset) FileUtils.cp(asset.path, asset.fingerprinted_target) _set_permissions(asset.fingerprinted_target) end # @since 0.3.0 # @api private def compress_in_place!(asset) compressed = Compressor.new(asset.path, asset.configuration).compress _write(asset.path, compressed) unless compressed.nil? end # @since 0.3.0 # @api private def write_manifest_file _write(@configuration.manifest_path, JSON.dump(@manifest)) end # @since 0.1.0 # @api private def assets Dir.glob("#{@configuration.destination_directory}#{::File::SEPARATOR}**#{::File::SEPARATOR}*") end # @since 0.1.0 # @api private def _convert_to_url(path) path.sub(public_directory.to_s, URL_REPLACEMENT) .gsub(File::SEPARATOR, URL_SEPARATOR) end # @since 0.1.0 # @api private def _write(path, content) Pathname.new(path).dirname.mkpath ::File.write(path, content) _set_permissions(path) end # @since 0.1.0 # @api private def _set_permissions(path) ::File.chmod(DEFAULT_PERMISSIONS, path) end # @since 0.3.0 # @api private def _configuration_for(asset) url = _convert_to_url(asset) configurations.find { |config| url.start_with?(config.prefix) } || @configuration end # @since 0.1.0 # @api private def public_directory @configuration.public_directory end # @since 0.4.0 # @api private def configurations if @duplicates.empty? [@configuration] else @duplicates.map { |dup| dup.respond_to?(:configuration) ? dup.configuration : dup } end end end end end