├── .gitignore ├── .github └── ISSUE_TEMPLATE.md ├── README.md ├── package.json ├── index.js ├── util.js └── hookNpm.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Version: 5 | 6 | ### OS: 7 | 8 | ### Node version: 9 | 10 | ### Log: 11 | ``` 12 | 13 | PASTE HERE 14 | 15 | ``` 16 | 17 | 18 | ### Description of the problem 19 | 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-adblock 2 | 3 | The time has come! Banish the ads and declutter the build logs! 4 | 5 | # How does it work? 6 | 7 | After being installed globally, this module's postinstall script will patch the nodeJS files in such a way that: 8 | - When npm gets updated, it gets repatched 9 | - When a module tries to run a postinstall hook for ads, it will silently print it to the debug log 10 | 11 | # Usage 12 | 13 | Simply install it globally using `npm i -g npm-adblock` 14 | 15 | You can re-run it at any time using `adblock-patch` 16 | 17 | When you don't notice it, it's working! :) 18 | 19 | Otherwise, feel free to [open an issue](https://github.com/mkg20001/npm-adblock/issues) 20 | 21 | # Note 22 | 23 | While I don't hate people asking for funds, there should be an easy and clear opt-out method, like on the web, which I did not find 24 | 25 | So I made this instead 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-adblock", 3 | "version": "0.1.9", 4 | "description": "The time has come! Banish the ads and declutter the build logs!", 5 | "main": "index.js", 6 | "bin": { 7 | "adblock-patch": "./hookNpm.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1", 11 | "postinstall": "node ./hookNpm.js" 12 | }, 13 | "keywords": [ 14 | "adblock", 15 | "block", 16 | "ads" 17 | ], 18 | "author": "Maciej Krüger ", 19 | "license": "MPL-2.0", 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/mkg20001/npm-adblock-patch.git" 23 | }, 24 | "bugs": { 25 | "url": "https://github.com/mkg20001/npm-adblock-patch/issues" 26 | }, 27 | "homepage": "https://github.com/mkg20001/npm-adblock-patch#readme", 28 | "dependencies": { 29 | "graceful-fs": "^4.2.2", 30 | "shell-escape": "^0.2.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const blacklist = ['sweetalert2', 'greenlock', 'funding'] 4 | 5 | function checkHook (pkgName, hookName, hookCmd) { 6 | // opencollective 7 | if (hookCmd.startsWith('opencollective-postinstall') || hookCmd.indexOf('opencollective.com') !== -1) { 8 | return false 9 | } 10 | 11 | // core-js 12 | if (hookCmd.endsWith('echo "ignore"')) { 13 | return false 14 | } 15 | 16 | if (hookName === 'postinstall') { 17 | // custom 18 | if (blacklist.indexOf(pkgName) !== -1) { 19 | return false 20 | } 21 | } 22 | 23 | return true 24 | } 25 | 26 | module.exports = { 27 | filterHook: (pkg, hookName) => { 28 | let pkgName 29 | 30 | if ((pkgName = pkg.package.name) === 'npm') { // we're self-updatig 31 | if (hookName === 'postinstall') { 32 | require('./hookNpm') // :tada: (we HAVE to hack this in here, seems like they are doing su to switch users) 33 | } 34 | } 35 | 36 | if (!pkg.package.scripts) return true 37 | let hookCmd 38 | if (!(hookCmd = pkg.package.scripts[hookName])) return true 39 | 40 | return checkHook(pkgName, hookName, hookCmd) 41 | }, 42 | isAdHook: checkHook 43 | } 44 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | global.DEBUG = [] 4 | const log = (...a) => process.env.DEBUG ? console.log(...a) : global.DEBUG.push(a) 5 | const err = (...a) => { 6 | console.error(' === DEBUG LOG === ') 7 | global.DEBUG.forEach((a) => console.error(...a)) 8 | console.error(' === END DEBUG LOG === ') 9 | console.error(...a) 10 | console.error(' === Rerun this script with the "adblock-patch" command ===') 11 | console.error() 12 | process.exit(0) 13 | } 14 | const errB = (msg, ...a) => { 15 | err(`\n *** ${msg} *** \n`, ...a) 16 | } 17 | 18 | const fs = require('graceful-fs') 19 | const path = require('path') 20 | 21 | /* Patches this: 22 | 23 | 'use strict' 24 | var lifecycle = require('../../utils/lifecycle.js') 25 | var packageId = require('../../utils/package-id.js') 26 | const fs = require('fs') 27 | 28 | module.exports = function (staging, pkg, log, next) { 29 | log.silly('postinstall', packageId(pkg)) 30 | lifecycle(pkg.package, 'postinstall', pkg.path, next) 31 | } 32 | */ 33 | 34 | const selfPath = fs.realpathSync(require.resolve('.')) 35 | 36 | log('hook path is %s', selfPath) 37 | 38 | function patchHook (contents, name) { 39 | if (contents.indexOf('filterHook') !== -1) return contents // already patched 40 | 41 | return contents 42 | .replace("strict'", "strict';" + `const {filterHook} = require(${JSON.stringify(selfPath)})`) 43 | .replace(/lifecycle\(.+\)/gmi, (full) => { 44 | return `if (filterHook(pkg, ${JSON.stringify(name)})) {${full};} else {next();}` 45 | }) 46 | } 47 | 48 | function guessNpmLocation () { 49 | let guesses = [] 50 | 51 | // take it from env 52 | if (process.env.npm_execpath) { 53 | guesses.push(path.dirname(path.dirname(path.dirname(process.env.npm_execpath)))) 54 | } 55 | guesses.push(path.dirname(path.dirname(path.dirname(process.argv[0])))) 56 | guesses.push(path.dirname(path.dirname(path.dirname(fs.realpathSync(process.argv[0]))))) 57 | if (process.env.npm_guess) { 58 | guesses.push(process.env.npm_guess) 59 | } 60 | 61 | // take it from our installation location 62 | guesses.push(path.dirname(path.dirname(require.resolve('.')))) 63 | 64 | if (process.platform === 'linux') { 65 | guesses.push('/usr/lib/node_modules') 66 | guesses.push('/usr/local/lib/node_modules') 67 | } else if (process.platform === 'win32') { 68 | guesses.push(path.join(process.env.APPDATA, 'npm', 'node_modules')) 69 | guesses.push('C:\\Program Files\\nodejs\\node_modules') 70 | } 71 | 72 | guesses = guesses.concat(module.paths) 73 | 74 | log('found %s guesses', guesses.length) 75 | log(guesses) 76 | 77 | const validGuesses = guesses.map(p => path.join(p, 'npm/lib/install/action/postinstall.js')).filter(p => fs.existsSync(p)).map(p => fs.realpathSync(p)) 78 | 79 | log('%s were valid', validGuesses.length) 80 | log(validGuesses) 81 | 82 | let _u = {} 83 | const validGuess = validGuesses.filter(p => { 84 | if (_u[p]) return false 85 | return (_u[p] = true) 86 | }) 87 | 88 | log('using %O', validGuess) 89 | 90 | if (!validGuess.length) { 91 | errB('Did not find any valid node paths. Please supply it via the environement variable npm_guess or as a cli argument') 92 | } 93 | 94 | if (validGuess.length !== 1) { 95 | errB('Found multiple valid guesses: %s. Please report!', validGuess.join(', ')) 96 | } 97 | 98 | return path.dirname(path.dirname(path.dirname(path.dirname(validGuess[0])))) 99 | } 100 | 101 | module.exports = { 102 | log, 103 | err, 104 | guessNpmLocation, 105 | patchHook 106 | } 107 | -------------------------------------------------------------------------------- /hookNpm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict' 4 | 5 | const { 6 | log, 7 | err, 8 | guessNpmLocation, 9 | patchHook 10 | } = require('./util') 11 | 12 | const path = require('path') 13 | const fs = require('graceful-fs') 14 | const os = require('os') 15 | const cp = require('child_process') 16 | 17 | const npmLocation = guessNpmLocation() 18 | 19 | const hooks = ['postinstall', 'preinstall', 'install'] 20 | 21 | const actionFolder = path.join(npmLocation, 'lib/install/action') 22 | const actions = hooks.map(h => [{ 23 | path: path.join(actionFolder, h + '.js'), 24 | name: h 25 | }]).map(a => a[0]) 26 | 27 | console.log('Installing npm patches for npm-adblock...') 28 | 29 | log('npm %o', npmLocation) 30 | log('actions %o', actionFolder) 31 | 32 | let tryAgainWithSudo = false 33 | let tryAgainWithUAC = false 34 | 35 | actions.forEach(({name, path}) => { 36 | try { 37 | log(path) 38 | 39 | const contents = String(fs.readFileSync(path)) 40 | const patchedContents = patchHook(contents, name) 41 | 42 | if (contents !== patchedContents) { 43 | fs.writeFileSync(path, patchedContents) 44 | } 45 | } catch (_err) { 46 | if (_err.code === 'EACCES') { 47 | if (!process.env.ADBLOCK_SUDO && process.platform === 'linux') { 48 | tryAgainWithSudo = true 49 | } else { 50 | err('\n *** Failed patching %s *** \n *** You NEED to run this script as an administrator or otherwise make the file accessible for patching! *** \n', path) 51 | } 52 | return 53 | } else if (_err.code === 'EPERM') { 54 | if (!process.env.ADBLOCK_SUDO && process.platform === 'linux') { 55 | tryAgainWithSudo = true 56 | } else if (!process.env.ADBLOCK_UAC && process.platform === 'win32') { 57 | tryAgainWithUAC = true 58 | } else { 59 | err('\n *** Failed patching %s *** \n *** You NEED to run this script as an administrator or otherwise make the file accessible for patching! *** \n', path) 60 | } 61 | return 62 | } 63 | 64 | err('\n *** Failed patching %s *** \n%s', path, _err.stack) 65 | } 66 | }) 67 | 68 | const escape = require('shell-escape') 69 | 70 | if (tryAgainWithSudo) { 71 | console.log('Couldn\'t install. Trying again with sudo/su...') 72 | 73 | const userInfo = (user) => { 74 | console.log('(If promted for a password, it is the one of the user %o)', user) 75 | console.log('(If you have never set a password for that user, simply press enter - it may fail, though)') 76 | } 77 | 78 | const trySudo = () => { 79 | userInfo(os.userInfo().username) 80 | return cp.spawn('sudo', process.argv, {env: Object.assign({ADBLOCK_SUDO: '1'}, process.env), stdio: 'inherit'}) 81 | } 82 | const trySu = () => { 83 | userInfo('root') 84 | return cp.spawn('su', ['root', '-s', '/bin/sh', '-c', escape(['sh', '-c', escape(process.argv)])], {env: Object.assign({ADBLOCK_SUDO: '1'}, process.env), stdio: 'inherit'}) 85 | } 86 | 87 | let t 88 | if (os.userInfo().username === 'nobody') { 89 | t = [trySu, trySudo] 90 | } else { 91 | t = [trySudo, trySu] 92 | } 93 | 94 | function tryMethod () { // eslint-disable-line no-inner-declarations 95 | let method = t.shift() 96 | 97 | if (!method) { 98 | err('\n *** Failed getting root privileges *** \n *** You NEED to run this script as an administrator or otherwise make the file accessible for patching! *** \n') 99 | } else { 100 | method().once('close', (code, sig) => { 101 | if (code || sig) { 102 | console.log('Failed with %o! Trying different method...', (code || sig)) 103 | tryMethod() 104 | } 105 | }) 106 | } 107 | } 108 | 109 | tryMethod() 110 | } else if (tryAgainWithUAC) { 111 | console.log('Couldn\'t install. Trying again with UAC...') 112 | 113 | const tmpPath = path.join(os.tmpdir(), String(Math.random()) + '.cmd') 114 | const tmpOutPath = path.join(os.tmpdir(), String(Math.random()) + '.txt') 115 | 116 | const SCRIPT = `@if (1==1) @if(1==0) @ELSE 117 | @echo off&SETLOCAL ENABLEEXTENSIONS 118 | >nul 2>&1 "%SYSTEMROOT%\\system32\\cacls.exe" "%SYSTEMROOT%\\system32\\config\\system"||( 119 | cscript //E:JScript //nologo "%~f0" 120 | @goto :EOF 121 | ) 122 | echo.Installing npm-adblock... 123 | setx ADBLOCK_UAC "1" 124 | setx npm_guess ${JSON.stringify(path.dirname(npmLocation))} /M 125 | ${process.argv.map(JSON.stringify).join(' ')} >${JSON.stringify(tmpOutPath)} 126 | REM https://stackoverflow.com/a/5969764/3990041 127 | @goto :EOF 128 | @end @ELSE 129 | ShA=new ActiveXObject("Shell.Application") 130 | ShA.ShellExecute("cmd.exe","/c \\""+WScript.ScriptFullName+"\\"","","runas",5); 131 | @end 132 | ` 133 | 134 | fs.writeFileSync(tmpPath, SCRIPT) 135 | const out = cp.execSync(tmpPath) 136 | console.log(String(out)) 137 | 138 | setTimeout(() => { 139 | fs.unlinkSync(tmpPath) 140 | console.log(String(fs.readFileSync(tmpOutPath))) 141 | fs.unlinkSync(tmpOutPath) 142 | }, 5 * 1000) 143 | } else { 144 | console.log('Installed successfully!') 145 | } 146 | --------------------------------------------------------------------------------