diff --git a/.circleci/config.yml b/.circleci/config.yml index abdfb35..0220c36 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,11 +4,11 @@ workflows: run_tests_with_coverage: jobs: - cover: - version: "10" + version: '10' - cover: - version: "lts" + version: 'lts' - cover: - version: "current" + version: 'current' jobs: cover: @@ -26,9 +26,9 @@ jobs: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- + - v1-dependencies-{{ checksum "package.json" }} + # fallback to using the latest cache if no exact match is found + - v1-dependencies- - run: npm install diff --git a/README.md b/README.md index 8bf80b7..a86f1a7 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -Create a hash checksum over a folder or a file. -The hashes are propagated upwards, the hash that is returned for a folder is generated over all the hashes of its children. +Create a hash checksum over a folder or a file. +The hashes are propagated upwards, the hash that is returned for a folder is generated over all the hashes of its children. The hashes are generated with the _sha1_ algorithm and returned in _base64_ encoding by default. Each file returns a name and a hash, and each folder returns additionally an array of children (file or folder elements). @@ -10,7 +10,7 @@ First, install folder-hash with `npm install --save folder-hash` or `yarn add fo ### Simple example -To see differences to the last version of this package, I would create hashes over all _.js_ and _.json_ files. But ignore everything inside folders starting with a dot, and also from the folders _node_modules_, _test_coverage_. The structure of the options object is documented below. +To see differences to the last version of this package, I would create hashes over all _.js_ and _.json_ files. But ignore everything inside folders starting with a dot, and also from the folders _node_modules_, _test_coverage_. The structure of the options object is documented below. This example is also stored in [./examples/readme-example1.js](/examples/readme-example1.js). ```js @@ -56,7 +56,7 @@ Creating a hash over the current folder: And the structure may be traversed to e.g. create incremental backups. -It is also possible to only match the full path and not the basename. The same configuration could look like this: +It is also possible to only match the full path and not the basename. The same configuration could look like this: _You should be aware that \*nix and Windows behave differently, so please use caution._ ```js @@ -138,6 +138,7 @@ const options = { ```js { algo: 'sha1', // see crypto.getHashes() for options in your node.js REPL + algoOptions: {}, // see https://nodejs.org/api/crypto.html#cryptocreatehashalgorithm-options -- only valid in node v12.8 and higher encoding: 'base64', // 'base64', 'base64url', 'hex' or 'binary' files: { exclude: [], @@ -336,7 +337,7 @@ const options = { ### Symlink options -Configure, how symbolic links should be hashed. +Configure, how symbolic links should be hashed. To understand how the options can be combined to create a specific behavior, look into [test/symbolic-links.js](https://github.com/marc136/node-folder-hash/blob/master/test/symbolic-links.js). @@ -509,11 +510,23 @@ hashElement(__dirname, options, (error, hash) => { console.log(hash.toString()); } }); + +// pass algoOptions (example: shake256) +// see https://nodejs.org/api/crypto.html#cryptocreatehashalgorithm-options -- only valid in node v12.8 and higher +const options = { algo: 'shake256', algoOptions:{ outputLength: 5 }, files: { exclude: ['.*'], matchBasename: true } }; +hashElement(__dirname, options, (error, hash) => { + if (error) { + return console.error('hashing failed:', error); + } else { + console.log('Result for folder "' + __dirname + '":'); + console.log(hash.toString()); + } +}); ``` ## Behavior -The behavior is documented and verified in the unit tests. Execute `npm test` or `mocha test`, and have a look at the _test_ subfolder. +The behavior is documented and verified in the unit tests. Execute `npm test` or `mocha test`, and have a look at the _test_ subfolder. You can also have a look at the [CircleCI report. ![CircleCI](https://circleci.com/gh/marc136/node-folder-hash/tree/master.svg?style=svg)](https://circleci.com/gh/marc136/node-folder-hash/tree/master) ### Creating hashes over files (with default options) diff --git a/index.js b/index.js index 59d9e0c..2a81b53 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ const defaultOptions = { algo: 'sha1', // see crypto.getHashes() for options + algoOptions: {}, encoding: 'base64', // 'base64', 'base64url', 'hex' or 'binary' files: { exclude: [], @@ -167,7 +168,7 @@ function prep(fs) { return new Promise((resolve, reject) => { try { - const hash = crypto.createHash(options.algo); + const hash = crypto.createHash(options.algo, options.algoOptions); if ( options.files.ignoreBasename || options.ignoreBasenameOnce || @@ -213,7 +214,7 @@ function prep(fs) { function symLinkIgnoreTargetContent(name, target, options, isRootElement) { delete options.skipMatching; // only used for the root level log.symlink('ignoring symbolic link target content'); - const hash = crypto.createHash(options.algo); + const hash = crypto.createHash(options.algo, options.algoOptions); if (!options.symbolicLinks.ignoreBasename && !(isRootElement && options.files.ignoreRootName)) { log.symlink('hash basename'); hash.update(name); @@ -237,7 +238,7 @@ function prep(fs) { const temp = await hashElementPromise(stats, dir, options, isRootElement); if (!options.symbolicLinks.ignoreTargetPath) { - const hash = crypto.createHash(options.algo); + const hash = crypto.createHash(options.algo, options.algoOptions); hash.update(temp.hash); log.symlink('hash targetpath'); hash.update(target); @@ -247,7 +248,7 @@ function prep(fs) { } catch (err) { if (options.symbolicLinks.ignoreTargetContentAfterError) { log.symlink(`Ignoring error "${err.code}" when hashing symbolic link ${name}`, err); - const hash = crypto.createHash(options.algo); + const hash = crypto.createHash(options.algo, options.algoOptions); if ( !options.symbolicLinks.ignoreBasename && !(isRootElement && options.files.ignoreRootName) @@ -314,6 +315,7 @@ function parseParameters(args) { if (!isObject(options_)) options_ = {}; const options = { algo: options_.algo || defaultOptions.algo, + algoOptions: options_.algoOptions || defaultOptions.algoOptions, encoding: options_.encoding || defaultOptions.encoding, files: Object.assign({}, defaultOptions.files, options_.files), folders: Object.assign({}, defaultOptions.folders, options_.folders), @@ -334,7 +336,7 @@ const HashedFolder = function HashedFolder(name, children, options, isRootElemen this.name = name; this.children = children; - const hash = crypto.createHash(options.algo); + const hash = crypto.createHash(options.algo, options.algoOptions); if ( options.folders.ignoreBasename || options.ignoreBasenameOnce || diff --git a/package.json b/package.json index e656e9e..0c58559 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "folder-hash", - "version": "4.0.4", + "version": "4.0.5", "description": "Create a hash checksum over a folder and its content - its children and their content", "main": "index.js", "bin": { diff --git a/test/base.js b/test/base.js index ceaccb6..21322aa 100644 --- a/test/base.js +++ b/test/base.js @@ -35,6 +35,36 @@ describe('Should generate hashes', function () { }; return hashElement(basename, dir, options).then(checkHash); }); + + it('with algoOptions passed', function () { + // base our expected hash on node version behavior + // algo options were not available until v12.8 + var v = process.version.split('.'); + var major = parseInt(v[0].replace('v', '')); + var minor = parseInt(v[1]); + var expectedHash = + major > 12 || (major === 12 && minor >= 8) + ? 'd89f885449' + : 'd89f8854493c06a3bea8deffaee1c43d7e29e8b140122f17829bb8ad73950cbc'; + + const checkAlgoOptionHash = result => { + should.exist(result); + should.exist(result.hash); + result.hash.should.equal(expectedHash); + }; + + var options = { + algo: 'shake256', + algoOptions: { outputLength: 5 }, + encoding: 'hex', + excludes: [], + match: { + basename: false, + path: false, + }, + }; + return hashElement(basename, dir, options).then(checkAlgoOptionHash); + }); }); describe('when executed with an error-first callback', function () {