Created
September 20, 2017 18:42
-
-
Save dmos62/5ac03373328a9d9cdc3288185f531547 to your computer and use it in GitHub Desktop.
Patcher for Node.js that uses hashes to not patch twice
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Usage looks like this: | |
``` | |
$ node auto-patch.js get \ | |
--original node_modules/epub.js/build/epub.js \ | |
--changed epubjs.changed > patches/epubjs.patch | |
$ node auto-patch.js apply patches/epubjs.patch | |
``` | |
The patch knows the hash of the resulting file, so, before applying, | |
it checks if the hashes are different. If they are the same, | |
it silently skips patching, so the second command above can be rerun | |
multiple times without corrupting the file. | |
Example automatic patching with npm: | |
{ | |
..., | |
"scripts": { | |
"patch-epubjs": "node auto-patch.js apply patches/epubjs.patch", | |
"prepare": "npm run patch-epubjs", | |
... | |
} | |
} | |
Requires: "node": ">=8.5.0" | |
*/ | |
const Crypto = require('crypto') | |
const DiffMatchPatch = new (require('diff-match-patch')) | |
const Fs = require('fs') | |
const debug = require('debug')('auto-patch') | |
const getHash = (s) => | |
{ | |
const hash = Crypto.createHash('sha256') | |
hash.update(s) | |
return hash.digest('hex') | |
} | |
const getPatch = (originalString, changedString) => | |
{ | |
const patches = DiffMatchPatch.patch_make(originalString, changedString) | |
return DiffMatchPatch.patch_toText(patches) | |
} | |
const applyPatch = (string, patch) => | |
{ | |
const patches = DiffMatchPatch.patch_fromText(patch) | |
return DiffMatchPatch.patch_apply(patches, string)[0] | |
} | |
const getStringContents = (path) => String(Fs.readFileSync(path)) | |
const getAutoPatch = (originalFile, changedFile, optPath = undefined) => | |
{ | |
const originalString = getStringContents(originalFile) | |
const changedString = getStringContents(changedFile) | |
const path = optPath || originalFile | |
const hash = getHash(changedString) | |
const patch = getPatch(originalString, changedString) | |
const json = | |
{ | |
path, | |
hash, | |
patch | |
} | |
return json | |
} | |
const overwriteFile = (path, stringContents) => | |
Fs.writeFileSync(path, stringContents) | |
const backupOriginal = (path) => Fs.copyFileSync(path, path + '.unpatched') | |
const applyAutoPatch = (autoPatch) => | |
{ | |
const path = autoPatch.path | |
const currentString = getStringContents(path) | |
const currentHash = getHash(currentString) | |
if (currentHash == autoPatch.hash) { | |
debug(`${path} is already patched. Skipping.`) | |
return | |
} | |
const patchedString = applyPatch(currentString, autoPatch.patch) | |
if (autoPatch.hash != getHash(patchedString)) { | |
console.error( | |
`Patch for ${path} is corrupted,` + | |
` specified hash does not match the resulting hash.` | |
) | |
return | |
} | |
debug(`Backing up and patching ${path}.`) | |
backupOriginal(path) | |
overwriteFile(path, patchedString) | |
} | |
const argv = require('minimist')(process.argv.slice(2)); | |
switch (true) { | |
case argv._[0] == 'get': | |
// usage: | |
// `node auto-patch.js get --original original.js --changed changed.js` | |
// optionally, --path, that the resulting patch will patch, | |
// can be specified separately. otherwise will be same as --original. | |
{ | |
const patch = getAutoPatch(argv.original, argv.changed, argv.path) | |
const json = JSON.stringify(patch) | |
process.stdout.write(json) | |
} | |
break | |
case argv._[0] == 'apply': | |
// usage: node auto-patch.js apply path/to/the.patch | |
{ | |
const json = getStringContents(argv._[1]) | |
const patch = JSON.parse(json) | |
applyAutoPatch(patch) | |
} | |
break | |
default: | |
console.error('Wrong arguments.') | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment