diff --git a/lib/bin.js b/lib/bin.js index 0b3a42c9..580a737e 100644 --- a/lib/bin.js +++ b/lib/bin.js @@ -3,9 +3,9 @@ const chalk = require('chalk'); const debug = require('debug')('npminstall:bin'); const path = require('path'); -const fs = require('mz/fs'); const cmdShim = require('cmd-shim-hotfix'); const normalize = require('npm-normalize-package-bin'); +const fixBin = require('bin-links/lib/fix-bin'); const utils = require('./utils'); module.exports = bin; @@ -39,7 +39,6 @@ async function bin(parentDir, pkg, pkgDir, options) { for (const name of names) { const srcBin = path.join(pkgDir, bins[name]); const destBin = path.join(binDir, name); - await fs.chmod(srcBin, 0o755); await linkBin(srcBin, destBin); if (showBinLog) { options.console.info('[%s] link %s@ -> %s', @@ -61,5 +60,8 @@ async function linkBin(src, dest) { }); } - return await utils.forceSymlink(src, dest); + await utils.forceSymlink(src, dest); + // ensure that bin are executable and not containing + // windows line-endings(CRLF) on the hashbang line + await fixBin(src, 0o755); } diff --git a/package.json b/package.json index 211e9dd2..9ccb91d0 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "agentkeepalive": "^4.0.2", "await-event": "^2.1.0", + "bin-links": "^2.3.0", "binary-mirror-config": "^1.19.0", "bytes": "^3.1.0", "chalk": "^2.4.2", diff --git a/test/bin.test.js b/test/bin.test.js index 46308ffd..099856ba 100644 --- a/test/bin.test.js +++ b/test/bin.test.js @@ -3,6 +3,7 @@ const assert = require('assert'); const path = require('path'); const fs = require('fs'); +const umask = process.umask(); const readJSON = require('../lib/utils').readJSON; const npminstall = require('./npminstall'); const helper = require('./helper'); @@ -35,4 +36,39 @@ describe('test/bin.test.js', () => { assert(pkg.name === '@bigfunger/decompress-zip'); assert(fs.existsSync(path.join(root, 'node_modules', '.bin', 'decompress-zip'))); }); + + it('fix windows hashbang', async () => { + const pkgs = [{ version: '../windows-shebang', type: 'local' }]; + await npminstall({ + root, + pkgs, + }); + const pkg = await readJSON(path.join(root, 'node_modules', 'windows-shebang', 'package.json')); + assert.equal(pkg.name, 'windows-shebang'); + assert.equal(pkg.version, '1.0.0'); + if (process.platform !== 'win32') { + /* eslint-disable no-bitwise */ + assert.equal(fs.statSync(path.join(root, 'node_modules/.bin/crlf')).mode & 0o755, 0o755 & (~umask)); + assert.equal( + fs.readFileSync(path.join(root, 'node_modules/.bin/crlf'), 'utf-8'), + '#!/usr/bin/env node\nconsole.log(\'crlf\');\r\n' + ); + /* eslint-disable no-bitwise */ + assert.equal(fs.statSync(path.join(root, 'node_modules/.bin/lf')).mode & 0o755, 0o755 & (~umask)); + assert.equal( + fs.readFileSync(path.join(root, 'node_modules/.bin/lf'), 'utf-8'), + '#!/usr/bin/env node\nconsole.log(\'lf\');\n' + ); + } else { + // don't change shebang file line + assert.equal( + fs.readFileSync(path.join(root, 'node_modules/.bin/crlf'), 'utf-8'), + '#!/usr/bin/env node\r\nconsole.log(\'crlf\');\r\n' + ); + assert.equal( + fs.readFileSync(path.join(root, 'node_modules/.bin/lf'), 'utf-8'), + '#!/usr/bin/env node\r\nconsole.log(\'lf\');\n' + ); + } + }); }); diff --git a/test/fixtures/windows-shebang/bin/crlf.js b/test/fixtures/windows-shebang/bin/crlf.js new file mode 100644 index 00000000..4dde0b8d --- /dev/null +++ b/test/fixtures/windows-shebang/bin/crlf.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +console.log('crlf'); diff --git a/test/fixtures/windows-shebang/bin/lf.js b/test/fixtures/windows-shebang/bin/lf.js new file mode 100644 index 00000000..d856b31b --- /dev/null +++ b/test/fixtures/windows-shebang/bin/lf.js @@ -0,0 +1,2 @@ +#!/usr/bin/env node +console.log('lf'); diff --git a/test/fixtures/windows-shebang/package.json b/test/fixtures/windows-shebang/package.json new file mode 100644 index 00000000..6fc7d395 --- /dev/null +++ b/test/fixtures/windows-shebang/package.json @@ -0,0 +1,8 @@ +{ + "name": "windows-shebang", + "version": "1.0.0", + "bin": { + "crlf": "bin/crlf.js", + "lf": "bin/lf.js" + } +}