Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.DS_Store
node_modules
.nyc_output
node_modules/
.nyc_output/
coverage/
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
language: node_js
sudo: false
node_js:
- "0.10"
- "0.11"
- "0.12"
- "iojs"
- "4"
- "5"
- "6"
after_success: npm run coverage
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,32 @@ optional-dev-dependency lodash redis@2.2.3 fffffffgggggg

All different [npm install styles](https://docs.npmjs.com/cli/install) are supported besides the git remote url

## Example
## Command Line

```
optional-dev-dependency package [options]

Options:
-s, --silent should the `npm install` output be shown?
[boolean] [default: false]
-S, --save try to install the specified packages, and save them to
optionalDevDependencies in package.json
[boolean] [default: false]
-t, --tag only try to load dependencies with this tag
-V, --verbose output NPM commands before running them
[boolean] [default: false]
-h, --help Show help [boolean]
-v, --version Show version number [boolean]

Examples:
../bin/odd.js lodash hiredis try to install 'lodash' and 'hiredis', it's okay
if an install fails.
../bin/odd.js -t travis try to install optionalDevDependencies for the
'travis' tag from package.json, it's okay if an
install fails.
```

## Examples

Here's an example from `node_redis`:

Expand All @@ -27,6 +52,48 @@ Here's an example from `node_redis`:
}
```

You can also save optionalDevDependencies in your `package.json` file for ease
of installation later. For example, let's say you want most developers who
download your package and play with it to run basic tests, but when you run your
code on your continuous integration server, you need a few extra dependencies.
You can use a `package.json` file like this:

```json
{
"name": "sample",
"scripts": {
"test": "mocha",
"precoverage": "optional-dev-dependency",
"coverage": "nyc npm test",
"preci": "optional-dev-dependency -t ci",
"ci": "nyc report --reporter=text-lcov | coveralls"
},
"optionalDevDependencies": {
"nyc": "^8.3.1",
"coveralls": [
"^2.11.14",
"ci"
]
}
}
```

When you do `npm run coverage`, the `precoverage` script will ensure that all
optionalDevDependencies without a tag are installed (in this case, `nyc`).

When you do `npm run ci` on your CI server, the `preci` script will ensure that
all optionalDevDependencies with the `ci` tag are installed (in this case,
`coveralls`).

You can load optionalDevDependencies into your `package.json` with the
--save/-S flag. This will install the latest lodash, save that version
in your `package.json`, and will only install it later if the `foo` or `bar`
tag is specified:

```shell
optional-dev-dependency --save lodash -t foo -t bar
```

## License

ISC
Expand Down
50 changes: 44 additions & 6 deletions bin/odd.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,60 @@ var yargs = require('yargs')
.usage('$0 package [options]')
.option('s', {
alias: 'silent',
boolean: true,
default: false,
describe: 'should the `npm install` output be shown?'
})
.option('S', {
alias: 'save',
boolean: true,
default: false,
describe: 'try to install the specified packages, and save them to optionalDevDependencies in package.json'
})
.option('t', {
alias: 'tag',
describe: 'only try to load dependencies with this tag'
})
.option('V', {
alias: 'verbose',
boolean: true,
default: false,
describe: 'output NPM commands before running them'
})
.example('$0 lodash hiredis', "try to install 'lodash' and 'hiredis', it's okay if an install fails.")
.example('$0 -t travis', "try to install optionalDevDependencies for the 'travis' tag from package.json, it's okay if an install fails.")
.alias('h', 'help')
.version(require('../package').version, 'v')
.version()
.alias('v', 'version')
.help('h')
var argv = yargs.argv
var optionalDevDependency = require('../')

var opts = {
stdio: argv.silent ? null : 'inherit',
tags: (typeof argv.tag === 'string') ? [argv.tag] : argv.tag,
verbose: argv.verbose
}

if (argv._.length === 0) {
yargs.showHelp()
} else {
optionalDevDependency(argv._, {
stdio: argv.silent ? null : 'inherit',
concurrency: argv.concurrency
optionalDevDependency.readPackage((er, pkg) => {
if (er) {
console.error(er.message)
process.exit(1)
}
argv._ = pkg.optionalDevDependencies
if (!argv._ || Object.keys(argv._).length < 1) {
yargs.showHelp()
process.exit(64)
} else {
optionalDevDependency(argv._, opts)
}
})
} else if (argv.save) {
optionalDevDependency.saveAll(argv._, opts, function () {
optionalDevDependency(argv._, opts)
})
} else {
optionalDevDependency(argv._, opts)
}

161 changes: 161 additions & 0 deletions dependency.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
'use strict'

const spawn = require('cross-spawn')
const path = require('path')
const fs = require('fs')

class Dependency {
// name could be 'foo', or 'foo@1' (if ver is null).
// ver is an array of [ver, tag, ...]
constructor (name, version) {
if (typeof name !== 'string') {
throw new TypeError('name must be string')
}
if (version == null) {
const parts = name.split('@')
this.name = parts[0]
this.version = (parts.length > 1) ? parts[1] : '*'
this.tags = []
} else {
this.name = name
this.tags = Dependency.toArray(version)
this.version = this.tags.shift()
}
}

get nameAtVersion () {
if (!this.version || (this.version === '*') || (this.version === 'latest')) {
return this.name
}
return this.name + '@' + this.version
}

getVersion (options, cb) {
if (typeof options === 'function') {
cb = options
options = { verbose: false }
}
const child = spawn('npm', ['info', this.name, 'version'], {
stdio: ['ignore', 'pipe', 'inherit']
})
const data = []
child.stdout.on('data', function (buf) { data.push(buf) })
child.once('error', cb)
child.once('close', () => {
this.version = '^' + Buffer.concat(data).toString('utf8').replace(/\n$/, '')
if (options.verbose) {
console.error('npm info ' + this.name + ' version: ' + this.version)
}
cb(null, this.version)
})
}

addTags (tags) {
tags = Dependency.toArray(tags)
if (tags.length > 0) {
for (const tag of Dependency.toArray(tags)) {
if (this.tags.indexOf(tag) === -1) {
this.tags.push(tag)
}
}
}
return this.tags
}

// is there a tag in list that is in tags, or does list have '*' in it?
isInTags (list) {
if ((this.tags.length === 0) || (this.tags.indexOf('*') > -1)) {
return '*'
}
list = Dependency.toArray(list)
for (var i = 0; i < list.length; i++) {
if (this.tags.indexOf(list[i]) > -1) {
return list[i]
}
}
return null
}

isInstalled () {
try {
const dir = path.join(Dependency.findNodeModules(), this.name)
require.resolve(path.join(dir))
return true
} catch (e) {
return false
}
}

install (options, cb) {
if (typeof options === 'function') {
cb = options
options = {}
}
const nav = this.nameAtVersion
if (options.verbose) {
console.error('npm install ' + nav)
}
const child = spawn('npm', ['install', nav], {stdio: options.stdio})
child.once('error', (er) => {
if (options.verbose) {
console.error('Error installing', er.message)
}
cb()
})
child.once('close', (code) => {
if (code !== 0) {
if (options.verbose) {
console.error('Error installing', nav, 'code', code, 'stdio', options.stdio)
}
}
cb()
})
}

toJSON () {
const ver = this.version || '*'
if (this.tags.length > 0) {
return [ver].concat(this.tags)
}
return ver
}

static toArray (stringOrArray) {
if (!stringOrArray) {
return []
}
if (typeof stringOrArray === 'string') {
return [stringOrArray]
} else if (Array.isArray(stringOrArray)) {
return stringOrArray
}
throw new TypeError('invalid input not string or array' + stringOrArray)
}

// Search up the path, starting at cwd, looking for node_modules
// Note: require works off of the path of *this* module by default, not
// from process.cwd(). Find the correct node_modules directory from cwd,
// and use absolute paths to load from that directory.
static findNodeModules (filename) {
filename = filename || 'node_modules'
if (Dependency.__root) {
return path.join(Dependency.__root, filename)
}
let parts = process.cwd().split(path.sep)
while (parts.length > 1) {
Dependency.__root = path.sep + path.join.apply(path, parts)
if (fs.existsSync(path.join(Dependency.__root, 'package.json'))) {
return path.join(Dependency.__root, filename)
}
parts.pop()
}
Dependency.clear()
throw new Error('Not in a node package hierarchy')
}

// Clear the cache of the project root
static clear () {
delete Dependency.__root
}
}
module.exports = Dependency
Loading