Take contents of a file (or stdin), and output as new file with a hash in the name
JavaScript

README.md

HashMark

Build Status

HashMark is a small utility which takes a file (or sdtin) as input, and writes the contents of the input to a file named with a hash digest of the file. This is useful for cache busting sticky caches - files can have far future expires headers and when the code changes, a new filename is created.

Examples:

Shell

cat file.js | ./bin/hashmark 'file.{hash}.js' # Writes to test.3eae1599bb7f187b86d6427942d172ba8dd7ee5962aab03e0839ad9d59c37eb0.js
> Computed hash: 3eae1599bb7f187b86d6427942d172ba8dd7ee5962aab03e0839ad9d59c37eb0
>
cat file.js | ./bin/hashmark -l 8 'file.{hash}.js' # Writes to test.3eae1599.js
> Computed hash: 3eae1599
>
cat file.js | ./bin/hashmark -l 4 -d md5 'dist/{hash}.js' # Writes to dist/cbd8.js
> Computed hash: cbd8
>

It is useful to use globs — meaning you can read in many files and it will output hashed versions of each:

./bin/hashmark path/to/*.js 'dist/{name}.{hash}.js'
./bin/hashmark path/to/{filea,fileb,filec}.js 'dist/{name}.{hash}.js'
./bin/hashmark **/*.js 'dist/{dir}/{name}.{hash}.js'
./bin/hashmark **/*.{js,css} 'dist/{dir}/{name}.{hash}{ext}'
./bin/hashmark **/*.{js,css} 'dist/{dir}/{hash}-{base}'

The above even works in shells that do not support globs (such as cmd.exe on Windows), because hashmark has support for expanding globs itself. Note that if your shell supports the * glob but not the ** glob (such as bash before version 4), the shell will interpret ** as two * in a row, which means that hashmark never receives the ** and therefore cannot expand it. In that case you need to quote your argument. Therefore, if you want to write cross-platform scripts it is best to always quote the args:

Available pattern keys includes all the keys returned by path.parse(filename) and hash (ie: hash, root, dir, name, base, ext).

./bin/hashmark 'dir/**/*.{js,css}' 'test/*.js' 'dist/{dir}/{name}.{hash}{ext}'

The hashmark command will output some JSON to stdout with a map of filenames and their new hashes, meaning you can pipe the hash to other programs. To make hashmark completely silent - simply pass the --silent or -s flag.

./bin/hashmark -l 4 file.js 'dist/{hash}.js' --silent

You can also output the JSON map to a file, by passing the --asset-map or -m flag. It will still be logged to stdout unless you pass --silent

./bin/hashmark -l 4 file.js 'dist/{hash}.js' --asset-map assets.json

You can specify from which directory to work from with --cwd or -c. Note: asset-map will be relative to this directory.

mkdir dist/subdir
echo 'abracadabra' > dist/subdir/file.js
./bin/hashmark --cwd dist -d md5 -l 8 '**/*.js' '{dir}/{name}-{hash}{ext}'
> {"subdir/file.js":"subdir/file-97640ef5.js"}

Integrations

replaceinfiles

Now that your assets have been renamed, you might want to update references to them in some other files (ex:background image reference in your css files). That's exactly what replaceinfiles is made for. Just pipe it to hashmark. It just work™. Full example.

Programmatically

The hashmark function can be used programmatically. You can pass it a String, Buffer or Stream as the first argument, an options object as the second argument, and a callback as the third.

The callback receives an error as the first argument (or null) and an object which maps each given file to the newly hashed file name.

var hashmark = require('hashmark');
var file = fs.createReadStream('file.js');

hashmark(file, { length: 8, digest: 'md5', 'pattern': '{hash}'}, function (err, map) {
    console.log(map);
});

The function also returns an event emitter which emits error, file and end events. File events get fired when an individual file has been hashed, and the end event is fired when all have been done. file is given two arguments - the files old name, and the new calculated filename (given the template string), and the end event is given an object mapping of all files.

var hashmark = require('hashmark');
var file = fs.createReadStream('file.js');

hashmark(file, { length: 8, digest: 'md5', pattern: 'hash'})
    .on('file', function (oldFileName, newFileName) {
        console.log('File hashed!', oldFileName, newFileName);
    })
    .on('end', function (jsonMap) {
        console.log('~FIN');
    })

Files can be a single Stream, or filename String, or an Array of Streams and/or filename Strings.

var hashmark = require('hashmark');
var file = fs.createReadStream('file.js');

hashmark([file, 'file2.js'], { length: 8, digest: 'md5', file: 'file.#.js'}, function (err, hash) {
    console.log('File written to file.' + hash + '.js');
    console.log(hash);
});

Contributing

This is such a small utility - there's very little point in contributing. If it doesn't do something that you really think it should, feel free to raise an issue - just be aware I might say no. If you can make it faster/better/stronger without changing the API/functionality then send a PR!