Skip to content

Commit

Permalink
fix(os): proper support for windows os (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
triforcely committed Feb 22, 2019
1 parent dfcf014 commit 2dc9dc9
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 40 deletions.
88 changes: 77 additions & 11 deletions __tests__/app.test.js
@@ -1,5 +1,10 @@
/* eslint-disable security/detect-child-process */
const TerminalLauncher = require('../index')
const getTerminals = require('../lib/get-terminals')

const opn = require('opn')
const childProcess = require('child_process')

jest.mock('opn')

describe('Terminal Launcher', () => {
Expand All @@ -9,20 +14,81 @@ describe('Terminal Launcher', () => {
}).toThrow()
})

test('launching a terminal calls opn with the correct params', async () => {
const path = '/usr/local/bin/non-existent.sh'
describe('Linux', () => {
beforeAll(() => {
Object.defineProperty(process, 'platform', {
value: 'linux'
})
})

test('launching a terminal calls opn with the correct params when running on linux', async () => {
const path = '/usr/local/bin/non-existent.sh'

await TerminalLauncher.launchTerminal({path})
expect(opn).toHaveBeenCalled()

await TerminalLauncher.launchTerminal({path})
expect(opn).toHaveBeenCalled()
const calledWithFirstArgument = opn.mock.calls[0][0]
expect(calledWithFirstArgument).toEqual(path)
})

const calledWithFirstArgument = opn.mock.calls[0][0]
expect(calledWithFirstArgument).toEqual(path)
describe('get-terminals returns array of functions', () => {
test('terminals are a list of functions', () => {
const terminals = getTerminals('mock.ext')
expect(Array.isArray(terminals)).toBeTruthy()
expect(terminals.length).toBeTruthy()
expect(typeof terminals[0] === 'function').toBeTruthy()
})
})
})

test('terminals are a list of functions', () => {
const terminals = TerminalLauncher.getTerminals()
expect(Array.isArray(terminals)).toBeTruthy()
expect(terminals.length).toBeTruthy()
expect(typeof terminals[0] === 'function').toBeTruthy()
describe('Windows', () => {
beforeAll(() => {
Object.defineProperty(process, 'platform', {
value: 'win32'
})

Object.defineProperty(process, 'env', {
value: {
PATHEXT: '.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC'
}
})
})

beforeEach(() => {
childProcess.exec = jest.fn(() => ({once: (type, c) => type === 'close' && c(0)}))
})

describe('launching a terminal calls child_process.exec with the correct command', () => {
test('for powershell scripts', async () => {
const path = 'C:\\script.ps1'
const expectedCommand = 'start powershell "C:\\script.ps1"'

await TerminalLauncher.launchTerminal({path})
expect(childProcess.exec).toHaveBeenCalled()

const calledWithFirstArgument = childProcess.exec.mock.calls[0][0]
expect(calledWithFirstArgument).toEqual(expectedCommand)
})

test('for bat scripts', async () => {
const path = 'C:\\script.bat'
const expectedCommand = 'start cmd /c "C:\\script.bat"'

await TerminalLauncher.launchTerminal({path})
expect(childProcess.exec).toHaveBeenCalled()

const calledWithFirstArgument = childProcess.exec.mock.calls[0][0]
expect(calledWithFirstArgument).toEqual(expectedCommand)
})
})

describe('get-terminals returns array of functions', () => {
test('terminals are a list of functions', () => {
const terminals = getTerminals('mock.ext')
expect(Array.isArray(terminals)).toBeTruthy()
expect(terminals.length).toBeTruthy()
expect(typeof terminals[0] === 'function').toBeTruthy()
})
})
})
})
35 changes: 6 additions & 29 deletions lib/TerminalLauncher.js
@@ -1,44 +1,21 @@
'use strict'
const opn = require('opn')
const getTerminals = require('./get-terminals')
const debug = require('debug')('opn-shell')

const terminalAppsInfo = [
// MacOS variations of terminal apps
'Hyper',
'iTerm',
'terminal.app',
// Linux variations of terminal apps
['x-terminal-emulator', '-e'],
['gnome-terminal', '-e'],
['konsole', '-e'],
['xterm', '-e'],
['urxvt', '-e'],
[process.env.COLORTERM, '-e'],
[process.env.XTERM, '-e']
]

class TerminalLauncher {
static launchTerminal({path} = {}) {
if (!path) {
throw new Error('no program path provided to launch')
}

debug('executing: %s', path)
const shellLauncher = TerminalLauncher.getTerminals(path).reduce((promise, nextPromise) => {
return promise.catch(nextPromise)
}, Promise.reject()) /* eslint prefer-promise-reject-errors: "off" */

return shellLauncher
}
const shellLauncher = getTerminals(path).reduce(
(promise, nextPromise) => promise.catch(nextPromise),
Promise.reject()
) /* eslint prefer-promise-reject-errors: "off" */

static getTerminals(path) {
const terminalApps = terminalAppsInfo.map(appInfo => {
debug('adding terminal configuration: %s', appInfo.toString())
return () => opn(path, {app: appInfo})
})

terminalApps.push(() => opn(path))
return terminalApps
return shellLauncher
}
}

Expand Down
31 changes: 31 additions & 0 deletions lib/get-terminals-linux.js
@@ -0,0 +1,31 @@
'use strict'

const opn = require('opn')
const debug = require('debug')('opn-shell')

function getLinuxTerminalHandlers(path) {
const terminalApps = [
// MacOS variations of terminal apps
'Hyper',
'iTerm',
'terminal.app',
// Linux variations of terminal apps
['x-terminal-emulator', '-e'],
['gnome-terminal', '-e'],
['konsole', '-e'],
['xterm', '-e'],
['urxvt', '-e'],
[process.env.COLORTERM, '-e'],
[process.env.XTERM, '-e']
]

const handlers = terminalApps.map(appInfo => {
debug('adding terminal configuration: %s', appInfo.toString())
return () => opn(path, {app: appInfo})
})

handlers.push(() => opn(path))
return handlers
}

module.exports = getLinuxTerminalHandlers
42 changes: 42 additions & 0 deletions lib/get-terminals-windows.js
@@ -0,0 +1,42 @@
/* eslint-disable security/detect-child-process */
'use strict'

const debug = require('debug')('opn-shell')
const path = require('path')
const childProcess = require('child_process')

function getWindowsTerminalHandlers(scriptPath) {
const extension = path.extname(scriptPath).toLowerCase()
const logAddedTerminal = terminal => debug(`'adding terminal configuration: ${terminal}'`)

let command
if (getCmdSupportedExtensions().includes(extension)) {
command = `start cmd /c "${scriptPath}"`
logAddedTerminal('cmd')
} else {
command = `start powershell "${scriptPath}"`
logAddedTerminal('powershell')
}

const handler = () =>
new Promise((resolve, reject) => {
const cp = childProcess.exec(command)

cp.once('error', reject)
cp.once(
'close',
code => (code === 0 ? resolve(cp) : reject(new Error('Exited with code ' + code)))
)
})

return [handler]
}

function getCmdSupportedExtensions() {
// PATHEXT contains semicolon separated list of file extensions which are considered to be executable by Windows
// (runnable by cmd)
const extensions = process.env.PATHEXT.toLowerCase().split(';')
return extensions
}

module.exports = getWindowsTerminalHandlers
10 changes: 10 additions & 0 deletions lib/get-terminals.js
@@ -0,0 +1,10 @@
'use strict'

function getTerminals(executablePath) {
const module = /^win/i.test(process.platform)
? './get-terminals-windows'
: './get-terminals-linux'
return require(module)(executablePath)
}

module.exports = getTerminals

0 comments on commit 2dc9dc9

Please sign in to comment.