From b712f2ce9541571a82f3f6085d6ffa0fc45024c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kat=20March=C3=A1n?= Date: Sat, 24 Jun 2017 00:30:02 +0100 Subject: [PATCH] feat(local): improve the interaction with local pkgs BREAKING CHANGE: `npx ./something` will now execute `./something` as a binary instead of trying to install it as npm would. --- index.js | 41 ++++++++++++++++++++++++-------------- parse-args.js | 17 ++++++++++++---- test/guess-command-name.js | 13 +++++++----- 3 files changed, 47 insertions(+), 24 deletions(-) diff --git a/index.js b/index.js index 5612a96..3321a3d 100755 --- a/index.js +++ b/index.js @@ -125,6 +125,8 @@ module.exports._getExistingPath = getExistingPath function getExistingPath (command, opts) { if (opts.cmdHadVersion || opts.packageRequested || opts.ignoreExisting) { return Promise.resolve(false) + } else if (opts.isLocal) { + return Promise.resolve(command) } else { return which(command).catch(err => { if (err.code === 'ENOENT') { @@ -182,9 +184,9 @@ function installPackages (specs, prefix, opts) { module.exports._execCommand = execCommand function execCommand (_existing, argv) { - return findNodeScript(_existing).then(existing => { + return findNodeScript(_existing, argv).then(existing => { const Module = require('module') - if (existing && Module.runMain && !argv.shell) { + if (existing && Module.runMain && !argv.shell && existing !== __filename) { // let it take over the process. This means we can skip node startup! if (!argv.noYargs) { // blow away built-up yargs crud @@ -211,22 +213,31 @@ function execCommand (_existing, argv) { } module.exports._findNodeScript = findNodeScript -function findNodeScript (existing) { +function findNodeScript (existing, opts) { if (!existing || process.platform === 'win32') { return Promise.resolve(false) } else { - // NOTE: only *nix is supported for process-replacement juggling - const line = '#!/usr/bin/env node\n' - const bytecount = line.length - const buf = Buffer.alloc(bytecount) - return promisify(fs.open)(existing, 'r').then(fd => { - return promisify(fs.read)(fd, buf, 0, bytecount, 0).then(() => { - return promisify(fs.close)(fd) - }, err => { - return promisify(fs.close)(fd).then(() => { throw err }) - }) - }).then(() => { - return buf.toString('utf8') === line && existing + return promisify(fs.stat)(existing).then(stat => { + if (opts.isLocal && stat.isDirectory()) { + // npx will execute the directory itself + try { + const pkg = require(path.resolve(existing, 'package.json')) + return path.resolve(existing, pkg.main || 'index.js') + } catch (e) {} + } else { + const line = '#!/usr/bin/env node\n' + const bytecount = line.length + const buf = Buffer.alloc(bytecount) + return promisify(fs.open)(existing, 'r').then(fd => { + return promisify(fs.read)(fd, buf, 0, bytecount, 0).then(() => { + return promisify(fs.close)(fd) + }, err => { + return promisify(fs.close)(fd).then(() => { throw err }) + }) + }).then(() => { + return buf.toString('utf8') === line && existing + }) + } }) } } diff --git a/parse-args.js b/parse-args.js index 165ec80..09ab521 100644 --- a/parse-args.js +++ b/parse-args.js @@ -42,12 +42,13 @@ function parseArgs (argv) { parsed.command = parsed.package ? argv[cmdIndex] : guessCmdName(parsedCmd) + parsed.isLocal = parsedCmd.type === 'directory' parsed.cmdOpts = argv.slice(cmdIndex + 1) if (typeof parsed.package === 'string') { parsed.package = [parsed.package] } parsed.packageRequested = !!parsed.package - parsed.cmdHadVersion = parsed.package + parsed.cmdHadVersion = parsed.package || parsedCmd.type === 'directory' ? false : parsedCmd.name !== parsedCmd.raw const pkg = parsed.package || [argv[cmdIndex]] @@ -95,13 +96,21 @@ function fastPathArgs (argv) { } else { npa = require('npm-package-arg') parsedCmd = npa(argv[2]) - pkg = [parsedCmd.toString()] + if (parsedCmd.type === 'directory') { + pkg = [] + } else { + pkg = [parsedCmd.toString()] + } } return { command: guessCmdName(parsedCmd), cmdOpts: argv.slice(3), packageRequested: false, - cmdHadVersion: parsedCmd.name !== parsedCmd.raw, + isLocal: parsedCmd.type === 'directory', + cmdHadVersion: ( + parsedCmd.name !== parsedCmd.raw && + parsedCmd.type !== 'directory' + ), package: pkg, p: pkg, shell: false, @@ -129,7 +138,7 @@ function guessCmdName (spec) { const match = spec.fetchSpec.match(/([a-z0-9-]+)(?:\.git)?$/i) return match[1] } else if (spec.type === 'directory') { - return path.basename(spec.fetchSpec) + return spec.raw } else if (spec.type === 'file' || spec.type === 'remote') { let ext = path.extname(spec.fetchSpec) if (ext === '.gz') { diff --git a/test/guess-command-name.js b/test/guess-command-name.js index 454df8c..80598b5 100644 --- a/test/guess-command-name.js +++ b/test/guess-command-name.js @@ -26,11 +26,14 @@ test('guesses git binaries', t => { t.done() }) -test('guesses local directory binaries', t => { - t.equal(guessCmdName('./foo'), 'foo') - t.equal(guessCmdName('./dir/foo'), 'foo') - t.equal(guessCmdName('../../../dir/foo'), 'foo') - t.equal(guessCmdName('C:\\Program Files\\node\\foo'), 'foo') +test('leaves local directory/file commands intact', t => { + t.equal(guessCmdName('./foo'), './foo') + t.equal(guessCmdName('./dir/foo'), './dir/foo') + t.equal(guessCmdName('../../../dir/foo'), '../../../dir/foo') + t.equal( + guessCmdName('C:\\Program Files\\node\\foo'), + 'C:\\Program Files\\node\\foo' + ) t.done() })