163 lines
4.2 KiB
JavaScript
163 lines
4.2 KiB
JavaScript
'use strict'
|
|
|
|
const cp = require('child_process') // eslint-disable-line security/detect-child-process
|
|
const path = require('path')
|
|
const {promisify} = require('util')
|
|
|
|
const fs = require('graceful-fs')
|
|
const flattenDeep = require('lodash.flattendeep')
|
|
const hasha = require('hasha')
|
|
const releaseZalgo = require('release-zalgo')
|
|
|
|
const PACKAGE_FILE = require.resolve('./package.json')
|
|
const TEN_MEBIBYTE = 1024 * 1024 * 10
|
|
const execFile = promisify(cp.execFile)
|
|
|
|
const readFile = {
|
|
async: promisify(fs.readFile),
|
|
sync: fs.readFileSync
|
|
}
|
|
|
|
const tryReadFile = {
|
|
async (file) {
|
|
return readFile.async(file).catch(() => null)
|
|
},
|
|
|
|
sync (file) {
|
|
try {
|
|
return fs.readFileSync(file)
|
|
} catch (err) {
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
|
|
const tryExecFile = {
|
|
async (file, args, options) {
|
|
return execFile(file, args, options)
|
|
.then(({stdout}) => stdout)
|
|
.catch(() => null)
|
|
},
|
|
|
|
sync (file, args, options) {
|
|
try {
|
|
return cp.execFileSync(file, args, options)
|
|
} catch (err) {
|
|
return null
|
|
}
|
|
}
|
|
}
|
|
|
|
const git = {
|
|
tryGetRef (zalgo, dir, head) {
|
|
const m = /^ref: (.+)$/.exec(head.toString('utf8').trim())
|
|
if (!m) return null
|
|
|
|
return zalgo.run(tryReadFile, path.join(dir, '.git', m[1]))
|
|
},
|
|
|
|
tryGetDiff (zalgo, dir) {
|
|
return zalgo.run(tryExecFile,
|
|
'git',
|
|
// Attempt to get consistent output no matter the platform. Diff both
|
|
// staged and unstaged changes.
|
|
['--no-pager', 'diff', 'HEAD', '--no-color', '--no-ext-diff'],
|
|
{
|
|
cwd: dir,
|
|
maxBuffer: TEN_MEBIBYTE,
|
|
env: Object.assign({}, process.env, {
|
|
// Force the GIT_DIR to prevent git from diffing a parent repository
|
|
// in case the directory isn't actually a repository.
|
|
GIT_DIR: path.join(dir, '.git')
|
|
}),
|
|
// Ignore stderr.
|
|
stdio: ['ignore', 'pipe', 'ignore']
|
|
})
|
|
}
|
|
}
|
|
|
|
function addPackageData (zalgo, pkgPath) {
|
|
const dir = path.dirname(pkgPath)
|
|
|
|
return zalgo.all([
|
|
dir,
|
|
zalgo.run(readFile, pkgPath),
|
|
zalgo.run(tryReadFile, path.join(dir, '.git', 'HEAD'))
|
|
.then(head => {
|
|
if (!head) return []
|
|
|
|
return zalgo.all([
|
|
zalgo.run(tryReadFile, path.join(dir, '.git', 'packed-refs')),
|
|
git.tryGetRef(zalgo, dir, head),
|
|
git.tryGetDiff(zalgo, dir)
|
|
])
|
|
.then(results => {
|
|
return [head].concat(results.filter(Boolean))
|
|
})
|
|
})
|
|
])
|
|
}
|
|
|
|
function computeHash (zalgo, paths, pepper, salt) {
|
|
const inputs = []
|
|
if (pepper) inputs.push(pepper)
|
|
|
|
if (typeof salt !== 'undefined') {
|
|
if (Buffer.isBuffer(salt) || typeof salt === 'string') {
|
|
inputs.push(salt)
|
|
} else if (typeof salt === 'object' && salt !== null) {
|
|
inputs.push(JSON.stringify(salt))
|
|
} else {
|
|
throw new TypeError('Salt must be an Array, Buffer, Object or string')
|
|
}
|
|
}
|
|
|
|
// TODO: Replace flattenDeep with Array#flat(Infinity) after node.js 10 is dropped
|
|
return zalgo.all(paths.map(pkgPath => addPackageData(zalgo, pkgPath)))
|
|
.then(furtherInputs => hasha(flattenDeep([inputs, furtherInputs]), {algorithm: 'sha256'}))
|
|
}
|
|
|
|
let ownHash = null
|
|
let ownHashPromise = null
|
|
function run (zalgo, paths, salt) {
|
|
if (!ownHash) {
|
|
return zalgo.run({
|
|
async () {
|
|
if (!ownHashPromise) {
|
|
ownHashPromise = computeHash(zalgo, [PACKAGE_FILE])
|
|
}
|
|
return ownHashPromise
|
|
},
|
|
sync () {
|
|
return computeHash(zalgo, [PACKAGE_FILE])
|
|
}
|
|
})
|
|
.then(hash => {
|
|
ownHash = Buffer.from(hash, 'hex')
|
|
ownHashPromise = null
|
|
return run(zalgo, paths, salt)
|
|
})
|
|
}
|
|
|
|
if (paths === PACKAGE_FILE && typeof salt === 'undefined') {
|
|
// Special case that allow the pepper value to be obtained. Mainly here for
|
|
// testing purposes.
|
|
return zalgo.returns(ownHash.toString('hex'))
|
|
}
|
|
|
|
paths = Array.isArray(paths) ? paths : [paths]
|
|
return computeHash(zalgo, paths, ownHash, salt)
|
|
}
|
|
|
|
module.exports = (paths, salt) => {
|
|
try {
|
|
return run(releaseZalgo.async(), paths, salt)
|
|
} catch (err) {
|
|
return Promise.reject(err)
|
|
}
|
|
}
|
|
module.exports.sync = (paths, salt) => {
|
|
const result = run(releaseZalgo.sync(), paths, salt)
|
|
return releaseZalgo.unwrapSync(result)
|
|
}
|