Skip to content
This repository has been archived by the owner on Jul 6, 2019. It is now read-only.

Commit

Permalink
fix(perf): remove bluebird and defer some requires for SPEED
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat committed Jun 20, 2017
1 parent 5b7fe8d commit 00fc313
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 91 deletions.
33 changes: 17 additions & 16 deletions child.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
'use strict'

const BB = require('bluebird')

const cp = require('child_process')
const path = require('path')
const Y = require('./y.js')

module.exports.runCommand = runCommand
function runCommand (command, opts) {
Expand All @@ -13,31 +10,35 @@ function runCommand (command, opts) {
return spawn(cmd, copts, {
shell: opts.shell || !!opts.call,
stdio: opts.stdio || 'inherit'
}).catch({code: 'ENOENT'}, () => {
const err = new Error(
Y`npx: command not found: ${path.basename(cmd)}`
)
err.exitCode = 127
}).catch(err => {
if (err.code === 'ENOENT') {
err = new Error(
require('./y.js')`npx: command not found: ${path.basename(cmd)}`
)
err.exitCode = 127
}
throw err
})
}

module.exports.spawn = spawn
function spawn (cmd, args, opts) {
return BB.fromNode(cb => {
return new Promise((resolve, reject) => {
const child = cp.spawn(cmd, args, opts)
let stdout = ''
let stderr = ''
child.stdout && child.stdout.on('data', d => { stdout += d })
child.stderr && child.stderr.on('data', d => { stderr += d })
child.on('error', cb)
child.on('error', reject)
child.on('close', code => {
if (code) {
const err = new Error(Y`Command failed: ${cmd} ${args.join(' ')}`)
const err = new Error(
require('./y.js')`Command failed: ${cmd} ${args.join(' ')}`
)
err.exitCode = code
cb(err)
} else if (child.stdout || child.stderr) {
cb(null, {code, stdout, stderr})
reject(err)
} else {
resolve({code, stdout, stderr})
}
})
})
Expand All @@ -46,10 +47,10 @@ function spawn (cmd, args, opts) {
module.exports.exec = exec
function exec (cmd, args, opts) {
opts = opts || {}
return BB.fromNode(cb => {
return new Promise((resolve, reject) => {
cp.exec(`${escapeArg(cmd, true)} ${
args.join(' ')
}`, opts, cb)
}`, opts, (err, stdout) => err ? reject(err) : resolve(stdout))
})
}

Expand Down
58 changes: 38 additions & 20 deletions get-prefix.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
'use strict'

const BB = require('bluebird')

const statAsync = BB.promisify(require('fs').stat)
const statAsync = promisify(require('fs').stat)
const path = require('path')

module.exports = getPrefix
Expand All @@ -13,42 +11,62 @@ function getPrefix (current, root) {
root = path.dirname(root)
}
if (original !== root) {
return BB.resolve(root)
return Promise.resolve(root)
} else {
return getPrefix(root, root)
}
}
if (isRootPath(current)) {
return BB.resolve(root)
return Promise.resolve(root)
} else {
return BB.join(
return Promise.all([
fileExists(path.join(current, 'package.json')),
fileExists(path.join(current, 'node_modules')),
(hasPkg, hasModules) => {
if (hasPkg || hasModules) {
fileExists(path.join(current, 'node_modules'))
]).then(args => {
const hasPkg = args[0]
const hasModules = args[1]
if (hasPkg || hasModules) {
return current
} else {
const parent = path.dirname(current)
if (parent === current) {
// This _should_ only happen for root paths, but we already
// guard against that above. I think this is pretty much dead
// code, but npm was doing it, and I'm paranoid :s
return current
} else {
const parent = path.dirname(current)
if (parent === current) {
// This _should_ only happen for root paths, but we already
// guard against that above. I think this is pretty much dead
// code, but npm was doing it, and I'm paranoid :s
return current
} else {
return getPrefix(parent, root)
}
return getPrefix(parent, root)
}
}
)
})
}
}

function fileExists (f) {
return statAsync(f).catch({code: 'ENOENT'}, () => false)
return statAsync(f).catch(err => {
if (err.code !== 'ENOENT') {
throw err
}
})
}

function isRootPath (p) {
return process.platform === 'win32'
? p.match(/^[a-z]+:[/\\]?$/i)
: p === '/'
}

function promisify (f) {
const util = require('util')
if (util.promisify) {
return util.promisify(f)
} else {
return function () {
return new Promise((resolve, reject) => {
f.apply(this, [].slice.call(arguments).concat((err, val) => {
err ? reject(err) : resolve(val)
}))
})
}
}
}
91 changes: 55 additions & 36 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
#!/usr/bin/env node
'use strict'

const BB = require('bluebird')

const child = require('./child')
const dotenv = require('dotenv')
const getPrefix = require('./get-prefix.js')
const parseArgs = require('./parse-args.js')
const path = require('path')
const pkg = require('./package.json')
const updateNotifier = require('update-notifier')
const which = BB.promisify(require('which'))
const Y = require('./y.js')
const which = promisify(require('which'))

const PATH_SEP = process.platform === 'win32' ? ';' : ':'

Expand All @@ -34,7 +29,7 @@ function main (argv) {
}

if (!argv.call && (!argv.command || !argv.package)) {
console.error(Y`\nERROR: You must supply a command.\n`)
console.error(Y()`\nERROR: You must supply a command.\n`)
parseArgs.showHelp()
process.exitCode = 1
return
Expand All @@ -49,7 +44,7 @@ function main (argv) {
// Local project paths take priority. Go ahead and prepend it.
process.env.PATH = `${local}${PATH_SEP}${process.env.PATH}`
}
return BB.join(
return Promise.all([
// Figuring out if a command exists, early on, lets us maybe
// short-circuit a few things later. This bit here primarily benefits
// calls like `$ npx foo`, where we might just be trying to invoke
Expand All @@ -59,26 +54,27 @@ function main (argv) {
// we take a bit of extra time to pick up npm's full lifecycle script
// environment (so you can use `$npm_package_xxxxx` and company).
// Without that flag, we just use the current env.
argv.call && getEnv(argv),
(existing, newEnv) => {
if (newEnv) {
// NOTE - we don't need to manipulate PATH further here, because
// npm has already done so. And even added the node-gyp path!
process.env = newEnv
}
if ((!existing && !argv.call) || argv.packageRequested) {
// Some npm packages need to be installed. Let's install them!
return ensurePackages(argv.package, argv).then(results => {
console.error(Y`npx: installed ${
results.added.length + results.updated.length
} in ${(Date.now() - startTime) / 1000}s`)
}).then(() => existing)
} else {
// We can skip any extra installation, 'cause everything exists.
return existing
}
argv.call && getEnv(argv)
]).then(args => {
const existing = args[0]
const newEnv = args[1]
if (newEnv) {
// NOTE - we don't need to manipulate PATH further here, because
// npm has already done so. And even added the node-gyp path!
process.env = newEnv
}
).then(existing => {
if ((!existing && !argv.call) || argv.packageRequested) {
// Some npm packages need to be installed. Let's install them!
return ensurePackages(argv.package, argv).then(results => {
console.error(Y()`npx: installed ${
results.added.length + results.updated.length
} in ${(Date.now() - startTime) / 1000}s`)
}).then(() => existing)
} else {
// We can skip any extra installation, 'cause everything exists.
return existing
}
}).then(existing => {
return child.runCommand(existing, argv).catch(err => {
if (err.isOperational && err.exitCode) {
// At this point, we want to treat errors from the child as if
Expand All @@ -98,25 +94,25 @@ function main (argv) {

module.exports._localBinPath = localBinPath
function localBinPath (cwd) {
return getPrefix(cwd).then(prefix => {
return require('./get-prefix.js')(cwd).then(prefix => {
return path.join(prefix, 'node_modules', '.bin')
})
}

module.exports._getEnv = getEnv
function getEnv (opts) {
return child.exec(opts.npm, ['run', 'env']).then(dotenv.parse)
return child.exec(opts.npm, ['run', 'env']).then(require('dotenv').parse)
}

function ensurePackages (specs, opts) {
return (
opts.cache ? BB.resolve(opts.cache) : getNpmCache(opts)
opts.cache ? Promise.resolve(opts.cache) : getNpmCache(opts)
).then(cache => {
const prefix = path.join(cache, '_npx')
const bins = process.platform === 'win32'
? prefix
: path.join(prefix, 'bin')
return BB.promisify(require('rimraf'))(bins).then(() => {
return promisify(require('rimraf'))(bins).then(() => {
return installPackages(specs, prefix, opts)
}).then(info => {
// This will make temp bins _higher priority_ than even local bins.
Expand All @@ -131,11 +127,15 @@ function ensurePackages (specs, opts) {
module.exports._getExistingPath = getExistingPath
function getExistingPath (command, opts) {
if (opts.cmdHadVersion || opts.packageRequested || opts.ignoreExisting) {
return BB.resolve(false)
return Promise.resolve(false)
} else {
return which(command).catch({code: 'ENOENT'}, err => {
if (!opts.install) {
err.exitCode = 127
return which(command).catch(err => {
if (err.code === 'ENOENT') {
if (!opts.install) {
err.exitCode = 127
throw err
}
} else {
throw err
}
})
Expand Down Expand Up @@ -172,9 +172,28 @@ function installPackages (specs, prefix, opts) {
stdio: [0, 'pipe', 2]
}).then(deets => deets.stdout ? JSON.parse(deets.stdout) : null, err => {
if (err.exitCode) {
err.message = Y`Install for ${specs} failed with code ${err.exitCode}`
err.message = Y()`Install for ${specs} failed with code ${err.exitCode}`
}
throw err
})
})
}

function Y () {
return require('./y.js')
}

function promisify (f) {
const util = require('util')
if (util.promisify) {
return util.promisify(f)
} else {
return function () {
return new Promise((resolve, reject) => {
f.apply(this, [].slice.call(arguments).concat((err, val) => {
err ? reject(err) : resolve(val)
}))
})
}
}
}
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
},
"license": "CC0-1.0",
"dependencies": {
"bluebird": "^3.5.0",
"dotenv": "^4.0.0",
"gauge": "^2.7.4",
"npm": "^5.0.3",
Expand Down
Loading

0 comments on commit 00fc313

Please sign in to comment.