diff --git a/lib/fix-bin.js b/lib/fix-bin.js index 453bd4f..cb54e2f 100644 --- a/lib/fix-bin.js +++ b/lib/fix-bin.js @@ -4,6 +4,7 @@ const { chmod, open, readFile, + stat, } = require('fs/promises') const execMode = 0o777 & (~process.umask()) @@ -35,7 +36,8 @@ const dos2Unix = file => readFile(file, 'utf8').then(content => writeFileAtomic(file, content.replace(/^(#![^\n]+)\r\n/, '$1\n'))) -const fixBin = (file, mode = execMode) => chmod(file, mode) +const fixBin = (file, mode = execMode) => stat(file) + .then(st => (st.mode & mode) === mode ? null : chmod(file, mode)) .then(() => isWindowsHashbangFile(file)) .then(isWHB => isWHB ? dos2Unix(file) : null) diff --git a/test/fix-bin.js b/test/fix-bin.js index e17cde1..69786d0 100644 --- a/test/fix-bin.js +++ b/test/fix-bin.js @@ -93,6 +93,26 @@ t.test('custom exec mode', async t => { `#!/usr/bin/env node\nconsole.log('hello')\r\n`, 'fixed \\r on hashbang line') }) +t.test('skip chmod if file already has exec permissions', async t => { + const fsMock = { + ...fs.promises, + chmod: async () => { + throw new Error('chmod should not be called') + }, + } + const mockedFixBin = requireInject('../lib/fix-bin.js', { + 'fs/promises': fsMock, + }) + + const dir = t.testdir({ + execfile: `#!/usr/bin/env node\nconsole.log('hello')\n`, + }) + chmodSync(`${dir}/execfile`, 0o755) + // should not throw even though chmod is mocked to throw + await mockedFixBin(`${dir}/execfile`, 0o755) + t.equal((statSync(`${dir}/execfile`).mode & 0o755), 0o755 & (~umask), 'still has exec perms') +}) + t.test('custom exec mode in windows', async t => { const dir = t.testdir({ goodhb: `#!/usr/bin/env node\r\nconsole.log('hello')\r\n`,