Skip to content

Commit

Permalink
promisification
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Jan 27, 2020
1 parent c5f9b8e commit 48fd811
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 125 deletions.
22 changes: 6 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ On Unix systems, you should use a symbolic link instead.

[![Build Status](https://img.shields.io/travis/npm/cmd-shim/master.svg)](https://travis-ci.org/npm/cmd-shim)
[![Dependency Status](https://img.shields.io/david/npm/cmd-shim.svg)](https://david-dm.org/npm/cmd-shim)
[![NPM version](https://img.shields.io/npm/v/cmd-shim.svg)](https://www.npmjs.com/package/cmd-shim)
[![npm version](https://img.shields.io/npm/v/cmd-shim.svg)](https://www.npmjs.com/package/cmd-shim)

## Installation

Expand All @@ -17,28 +17,18 @@ npm install cmd-shim

## API

### cmdShim(from, to, cb)
### cmdShim(from, to) -> Promise

Create a cmd shim at `to` for the command line program at `from`.
e.g.

```javascript
var cmdShim = require('cmd-shim');
cmdShim(__dirname + '/cli.js', '/usr/bin/command-name', function (err) {
if (err) throw err;
});
cmdShim(__dirname + '/cli.js', '/usr/bin/command-name').then(() => {
// shims are created!
})
```

### cmdShim.ifExists(from, to, cb)
### cmdShim.ifExists(from, to) -> Promise

The same as above, but will just continue if the file does not exist.
Source:

```javascript
function cmdShimIfExists (from, to, cb) {
fs.stat(from, function (er) {
if (er) return cb()
cmdShim(from, to, cb)
})
}
```
94 changes: 36 additions & 58 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,64 +8,55 @@
// Write a binroot/pkg.bin + ".cmd" file that has this line in it:
// @<prog> <args...> %dp0%<target> %*

const {promisify} = require('util')
const fs = require('fs')
const writeFile = promisify(fs.writeFile)
const readFile = promisify(fs.readFile)
const chmod = promisify(fs.chmod)
const stat = promisify(fs.stat)
const unlink = promisify(fs.unlink)

const {dirname, relative} = require('path')
const mkdir = require('mkdirp')
const path = require('path')
const toBatchSyntax = require('./lib/to-batch-syntax')
const shebangExpr = /^#\!\s*(?:\/usr\/bin\/env)?\s*([^ \t]+=[^ \t]+\s+)*\s*([^ \t]+)(.*)$/

const cmdShimIfExists = (from, to, cb) => {
fs.stat(from, er => {
if (er) return cb()
cmdShim(from, to, cb)
})
}
const cmdShimIfExists = (from, to) =>
stat(from).then(() => cmdShim(from, to), () => {})

// Try to unlink, but ignore errors.
// Any problems will surface later.
const rm = (path, cb) => fs.unlink(path, () => cb())

const cmdShim = (from, to, cb) => {
fs.stat(from, (er, stat) => {
if (er)
return cb(er)
const rm = path => unlink(path).catch(() => {})

cmdShim_(from, to, cb)
})
}
const cmdShim = (from, to) =>
stat(from).then(() => cmdShim_(from, to))

const cmdShim_ = (from, to, cb) => {
const next = () => writeShim(from, to, cb)
const then = times(3, next, cb)

rm(to, then)
rm(to + '.cmd', then)
rm(to + '.ps1', then)
}
const cmdShim_ = (from, to) => Promise.all([
rm(to),
rm(to + '.cmd'),
rm(to + '.ps1'),
]).then(() => writeShim(from, to))

const writeShim = (from, to, cb) => {
const writeShim = (from, to) =>
// make a cmd file and a sh script
// First, check if the bin is a #! of some sort.
// If not, then assume it's something that'll be compiled, or some other
// sort of script, and just call it directly.
mkdir(path.dirname(to)).then(() => {
fs.readFile(from, 'utf8', (er, data) => {
if (er) return writeShim_(from, to, null, null, null, cb)
mkdir(dirname(to))
.then(() => readFile(from, 'utf8'))
.then(data => {
const firstLine = data.trim().split(/\r*\n/)[0]
const shebang = firstLine.match(shebangExpr)
if (!shebang) return writeShim_(from, to, null, null, null, cb)
if (!shebang) return writeShim_(from, to)
const vars = shebang[1] || ''
const prog = shebang[2]
const args = shebang[3] || ''
return writeShim_(from, to, prog, args, vars, cb)
})
}, cb)
}
return writeShim_(from, to, prog, args, vars)
}, er => writeShim_(from, to))


const writeShim_ = (from, to, prog, args, variables, cb) => {
let shTarget = path.relative(path.dirname(to), from)
const writeShim_ = (from, to, prog, args, variables) => {
let shTarget = relative(dirname(to), from)
let target = shTarget.split('/').join('\\')
let longProg
let shProg = prog && prog.split('\\').join('/')
Expand Down Expand Up @@ -225,31 +216,18 @@ const writeShim_ = (from, to, prog, args, variables, cb) => {
+ 'exit $LASTEXITCODE\n'
}

const next = () => chmodShim(to, cb)
const then = times(3, next, cb)
fs.writeFile(to + '.ps1', pwsh, 'utf8', then)
fs.writeFile(to + '.cmd', cmd, 'utf8', then)
fs.writeFile(to, sh, 'utf8', then)
}

const chmodShim = (to, cb) => {
const then = times(3, cb, cb)
fs.chmod(to, 0o755, then)
fs.chmod(to + '.cmd', 0o755, then)
fs.chmod(to + '.ps1', 0o755, then)
return Promise.all([
writeFile(to + '.ps1', pwsh, 'utf8'),
writeFile(to + '.cmd', cmd, 'utf8'),
writeFile(to, sh, 'utf8'),
]).then(() => chmodShim(to))
}

const times = (n, ok, cb) => {
let errState = null
return er => {
if (!errState) {
if (er)
cb(errState = er)
else if (--n === 0)
ok()
}
}
}
const chmodShim = to => Promise.all([
chmod(to, 0o755),
chmod(to + '.cmd', 0o755),
chmod(to + '.ps1', 0o755),
])

module.exports = cmdShim
cmdShim.ifExists = cmdShimIfExists
64 changes: 13 additions & 51 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,17 @@ var cmdShim = require('../')
test('no shebang', function (t) {
var from = path.resolve(fixtures, 'from.exe')
var to = path.resolve(fixtures, 'exe.shim')
cmdShim(from, to, function(er) {
if (er)
throw er
return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('if exists (it does exist)', function (t) {
var from = path.resolve(fixtures, 'from.exe')
var to = path.resolve(fixtures, 'exe.shim')
cmdShim.ifExists(from, to, function(er) {
if (er)
throw er
return cmdShim.ifExists(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
Expand All @@ -38,9 +33,7 @@ test('if exists (it does exist)', function (t) {
test('if exists (it does not exist)', function (t) {
var from = path.resolve(fixtures, 'argle bargle we like to sparkle')
var to = path.resolve(fixtures, 'argle-bargle-shim')
cmdShim.ifExists(from, to, function(er) {
if (er)
throw er
return cmdShim.ifExists(from, to).then(() => {
t.throws(() => fs.statSync(to))
t.throws(() => fs.statSync(to + '.cmd'))
t.throws(() => fs.statSync(to + '.ps1'))
Expand All @@ -51,112 +44,81 @@ test('if exists (it does not exist)', function (t) {
test('fails if from doesnt exist', t => {
var from = path.resolve(fixtures, 'argle bargle we like to sparkle')
var to = path.resolve(fixtures, 'argle-bargle-shim')
cmdShim(from, to, function(er) {
t.match(er, { code: 'ENOENT' })
t.end()
})
return t.rejects(cmdShim(from, to), { code: 'ENOENT' })
})

test('fails if mkdir fails', t => {
var from = path.resolve(fixtures, 'from.env')
var to = path.resolve(fixtures, 'from.env/a/b/c')
cmdShim(from, to, er => {
t.match(er, { code: /^(ENOTDIR|EEXIST)$/ })
t.end()
})
return t.rejects(cmdShim(from, to), { code: /^(ENOTDIR|EEXIST)$/ })
})

test('fails if to is a dir', t => {
var from = path.resolve(fixtures, 'from.env')
var to = path.resolve(fixtures)
cmdShim(from, to, er => {
t.match(er, { code: 'EISDIR' })
t.teardown(() => {
rimraf.sync(to + '.cmd')
rimraf.sync(to + '.ps1')
t.end()
})
return t.rejects(cmdShim(from, to), { code: 'EISDIR' })
})

test('just proceed if reading fails', t => {
var from = fixtures
var to = path.resolve(fixtures, 'env.shim')
cmdShim(from, to, er => {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('env shebang', function (t) {
var from = path.resolve(fixtures, 'from.env')
var to = path.resolve(fixtures, 'env.shim')
cmdShim(from, to, function(er) {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('env shebang with args', function (t) {
var from = path.resolve(fixtures, 'from.env.args')
var to = path.resolve(fixtures, 'env.args.shim')
cmdShim(from, to, function(er) {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('env shebang with variables', function (t) {
var from = path.resolve(fixtures, 'from.env.variables')
var to = path.resolve(fixtures, 'env.variables.shim')
cmdShim(from, to, function(er) {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('explicit shebang', function (t) {
var from = path.resolve(fixtures, 'from.sh')
var to = path.resolve(fixtures, 'sh.shim')
cmdShim(from, to, function(er) {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

test('explicit shebang with args', function (t) {
var from = path.resolve(fixtures, 'from.sh.args')
var to = path.resolve(fixtures, 'sh.args.shim')
cmdShim(from, to, function(er) {
if (er)
throw er

return cmdShim(from, to).then(() => {
matchSnapshot(t, fs.readFileSync(to, 'utf8'), 'shell')
matchSnapshot(t, fs.readFileSync(to + '.cmd', 'utf8'), 'cmd')
matchSnapshot(t, fs.readFileSync(to + '.ps1', 'utf8'), 'ps1')
t.end()
})
})

0 comments on commit 48fd811

Please sign in to comment.