Crypto = require(‘crypto’) Fs = require(‘fs’) File = require(‘path’) Convert = require(‘convert-source-map’)
module.exports = class TaskHelper extends require(‘./abstract’)
isEnabled: -> return false unless super() == true @getConfig().filerevision.enabled == true run: -> @mappingUpdatable = false @_readMappingFile() files = @collectFilesInBasePaths() files = @filterRevisionedFiles(files) files = @filterChangedFiles(files) @dropOldRevisionFiles(files) @createRevisionFiles(files) @updateSourceMapLink(files) @updateFileChangeTimestamps(files) @_writeMappingFile() # ----- getHashLength: -> @_hashLength or= 64 getRevisionedFileRegex: -> @_revisionedFileRegex or= new RegExp("\.[a-zA-Z0-9]{#{@getHashLength()}}\.") getMapping: -> @_mapping or= @_readMappingFile() getMappingFilePrettyPrint: -> @getConfig().filerevision.manifest.prettyPrint getCssBasePaths: -> return [] unless @_.isObject(@getAppConfig().css) p = "#{@getAppConfig().css.destPath}/#{@getAppConfig().css.destFile}" if @g.file.exists(p) then [File.dirname(p)] else [] getJsBasePaths: -> return [] unless @_.isObject(@getAppConfig().js) p = "#{@getAppConfig().js.destPath}/#{@getAppConfig().js.destFile}" if @g.file.exists(p) then [File.dirname(p)] else [] getImagesBasePaths: -> return [] unless @_.isObject(@getAppConfig().images) [File.join(@getAppConfig().images.destPath, @getAppConfig().images.destFolder)] getBasePaths: -> return @_basePaths unless @_.isEmpty(@_basePaths) @_basePaths = [ @getCssBasePaths(), @getCssBasePaths(), @getImagesBasePaths() ] @_basePaths = @_.uniq(@_.flatten(@_basePaths)) @_basePaths # ----- collectFilesInBasePaths: -> @_.inject @getBasePaths(), [], (memo, path) => memo = memo.concat(@g.file.expand(File.join(path, '{,*/}*'))) memo filterRevisionedFiles: (files) -> @_.filter files, (path) => @getRevisionedFileRegex().test(path) == false filterChangedFiles: (files) -> @_.filter files, (path) => @fileCacheHasChanged(path) dropOldRevisionFiles: (files) -> return unless @isEnabled() == true @_.each files, (path) => f = @getMapping()[path] @g.file.delete(f) if f && f != path && @g.file.exists(f) createRevisionFiles: (files) -> @_.each files, (path) => if @isEnabled() == true newPath = @_digestPath(path) @g.file.copy(path, newPath) else newPath = path @getMapping()[path] = newPath updateSourceMapLink: (files) -> @_.each files, (path) => sourceMapPath = "#{path}.map" resultFilePath = @getMapping()[path] return unless @g.file.exists(resultFilePath) return unless @g.file.exists(sourceMapPath) fileContents = @g.file.read(resultFilePath, {encoding: 'utf8'}) matches = Convert.mapFileCommentRegex.exec(fileContents) return unless matches sourceMapFile = matches[1] || matches[2] newSrcMap = fileContents.replace sourceMapFile, File.basename(sourceMapPath) @g.file.write resultFilePath, newSrcMap, {encoding: 'utf8'} updateFileChangeTimestamps: (files) -> @_.each files, (path) => @fileCacheUpdate(path) # ---------------------------------------------------------- # private # @nodoc _readMappingFile: -> return {} unless @g.file.exists(@getMappingFilePath()) try @_mapping = @g.file.readJSON(@getMappingFilePath()) catch e @_mapping = {} @_mapping or= {} @_mapping # @nodoc _writeMappingFile: -> spaces = if @getMappingFilePrettyPrint() == true then 4 else 0 mapping = JSON.stringify(@_mapping, null, spaces) @g.file.write @getMappingFilePath(), mapping, { encoding: 'utf-8' } # @nodoc _digestPath: (path) -> hash = Crypto.createHash('sha256').update(Fs.readFileSync(path)).digest('hex') hash = hash.slice(0, 64) fileData = File.parse(path) if /(css\.map)$/.test(path) newFileName = path.replace(/css\.map$/, '') newFileName += "#{hash}.css.map" return newFileName else if /(js\.map)$/.test(path) newFileName = path.replace(/js\.map$/, '') newFileName += "#{hash}.js.map" return newFileName else newFileName = "#{fileData.name}.#{hash}#{fileData.ext}" return File.join(fileData.dir, newFileName)