Skip to content

Commit b9c78d7

Browse files
hildjjbcoe
authored andcommitted
feat: install from optionalDevDependencies stanza in package.json
feat: install dependencies from tag.
1 parent 99136ba commit b9c78d7

10 files changed

Lines changed: 631 additions & 39 deletions

File tree

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.DS_Store
2-
node_modules
3-
.nyc_output
2+
node_modules/
3+
.nyc_output/
4+
coverage/

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
language: node_js
22
sudo: false
33
node_js:
4-
- "0.10"
5-
- "0.11"
6-
- "0.12"
7-
- "iojs"
4+
- "4"
5+
- "5"
6+
- "6"
87
after_success: npm run coverage

README.md

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,32 @@ optional-dev-dependency lodash [email protected] fffffffgggggg
1414

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

17-
## Example
17+
## Command Line
18+
19+
```
20+
optional-dev-dependency package [options]
21+
22+
Options:
23+
-s, --silent should the `npm install` output be shown?
24+
[boolean] [default: false]
25+
-S, --save try to install the specified packages, and save them to
26+
optionalDevDependencies in package.json
27+
[boolean] [default: false]
28+
-t, --tag only try to load dependencies with this tag
29+
-V, --verbose output NPM commands before running them
30+
[boolean] [default: false]
31+
-h, --help Show help [boolean]
32+
-v, --version Show version number [boolean]
33+
34+
Examples:
35+
../bin/odd.js lodash hiredis try to install 'lodash' and 'hiredis', it's okay
36+
if an install fails.
37+
../bin/odd.js -t travis try to install optionalDevDependencies for the
38+
'travis' tag from package.json, it's okay if an
39+
install fails.
40+
```
41+
42+
## Examples
1843

1944
Here's an example from `node_redis`:
2045

@@ -27,6 +52,48 @@ Here's an example from `node_redis`:
2752
}
2853
```
2954

55+
You can also save optionalDevDependencies in your `package.json` file for ease
56+
of installation later. For example, let's say you want most developers who
57+
download your package and play with it to run basic tests, but when you run your
58+
code on your continuous integration server, you need a few extra dependencies.
59+
You can use a `package.json` file like this:
60+
61+
```json
62+
{
63+
"name": "sample",
64+
"scripts": {
65+
"test": "mocha",
66+
"precoverage": "optional-dev-dependency",
67+
"coverage": "nyc npm test",
68+
"preci": "optional-dev-dependency -t ci",
69+
"ci": "nyc report --reporter=text-lcov | coveralls"
70+
},
71+
"optionalDevDependencies": {
72+
"nyc": "^8.3.1",
73+
"coveralls": [
74+
"^2.11.14",
75+
"ci"
76+
]
77+
}
78+
}
79+
```
80+
81+
When you do `npm run coverage`, the `precoverage` script will ensure that all
82+
optionalDevDependencies without a tag are installed (in this case, `nyc`).
83+
84+
When you do `npm run ci` on your CI server, the `preci` script will ensure that
85+
all optionalDevDependencies with the `ci` tag are installed (in this case,
86+
`coveralls`).
87+
88+
You can load optionalDevDependencies into your `package.json` with the
89+
--save/-S flag. This will install the latest lodash, save that version
90+
in your `package.json`, and will only install it later if the `foo` or `bar`
91+
tag is specified:
92+
93+
```shell
94+
optional-dev-dependency --save lodash -t foo -t bar
95+
```
96+
3097
## License
3198

3299
ISC

bin/odd.js

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,60 @@ var yargs = require('yargs')
66
.usage('$0 package [options]')
77
.option('s', {
88
alias: 'silent',
9+
boolean: true,
910
default: false,
1011
describe: 'should the `npm install` output be shown?'
1112
})
13+
.option('S', {
14+
alias: 'save',
15+
boolean: true,
16+
default: false,
17+
describe: 'try to install the specified packages, and save them to optionalDevDependencies in package.json'
18+
})
19+
.option('t', {
20+
alias: 'tag',
21+
describe: 'only try to load dependencies with this tag'
22+
})
23+
.option('V', {
24+
alias: 'verbose',
25+
boolean: true,
26+
default: false,
27+
describe: 'output NPM commands before running them'
28+
})
1229
.example('$0 lodash hiredis', "try to install 'lodash' and 'hiredis', it's okay if an install fails.")
30+
.example('$0 -t travis', "try to install optionalDevDependencies for the 'travis' tag from package.json, it's okay if an install fails.")
1331
.alias('h', 'help')
14-
.version(require('../package').version, 'v')
32+
.version()
1533
.alias('v', 'version')
1634
.help('h')
1735
var argv = yargs.argv
1836
var optionalDevDependency = require('../')
1937

38+
var opts = {
39+
stdio: argv.silent ? null : 'inherit',
40+
tags: (typeof argv.tag === 'string') ? [argv.tag] : argv.tag,
41+
verbose: argv.verbose
42+
}
43+
2044
if (argv._.length === 0) {
21-
yargs.showHelp()
22-
} else {
23-
optionalDevDependency(argv._, {
24-
stdio: argv.silent ? null : 'inherit',
25-
concurrency: argv.concurrency
45+
optionalDevDependency.readPackage((er, pkg) => {
46+
if (er) {
47+
console.error(er.message)
48+
process.exit(1)
49+
}
50+
argv._ = pkg.optionalDevDependencies
51+
if (!argv._ || Object.keys(argv._).length < 1) {
52+
yargs.showHelp()
53+
process.exit(64)
54+
} else {
55+
optionalDevDependency(argv._, opts)
56+
}
57+
})
58+
} else if (argv.save) {
59+
optionalDevDependency.saveAll(argv._, opts, function () {
60+
optionalDevDependency(argv._, opts)
2661
})
62+
} else {
63+
optionalDevDependency(argv._, opts)
2764
}
65+

dependency.js

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
'use strict'
2+
3+
const spawn = require('cross-spawn')
4+
const path = require('path')
5+
const fs = require('fs')
6+
7+
class Dependency {
8+
// name could be 'foo', or 'foo@1' (if ver is null).
9+
// ver is an array of [ver, tag, ...]
10+
constructor (name, version) {
11+
if (typeof name !== 'string') {
12+
throw new TypeError('name must be string')
13+
}
14+
if (version == null) {
15+
const parts = name.split('@')
16+
this.name = parts[0]
17+
this.version = (parts.length > 1) ? parts[1] : '*'
18+
this.tags = []
19+
} else {
20+
this.name = name
21+
this.tags = Dependency.toArray(version)
22+
this.version = this.tags.shift()
23+
}
24+
}
25+
26+
get nameAtVersion () {
27+
if (!this.version || (this.version === '*') || (this.version === 'latest')) {
28+
return this.name
29+
}
30+
return this.name + '@' + this.version
31+
}
32+
33+
getVersion (options, cb) {
34+
if (typeof options === 'function') {
35+
cb = options
36+
options = { verbose: false }
37+
}
38+
const child = spawn('npm', ['info', this.name, 'version'], {
39+
stdio: ['ignore', 'pipe', 'inherit']
40+
})
41+
const data = []
42+
child.stdout.on('data', function (buf) { data.push(buf) })
43+
child.once('error', cb)
44+
child.once('close', () => {
45+
this.version = '^' + Buffer.concat(data).toString('utf8').replace(/\n$/, '')
46+
if (options.verbose) {
47+
console.error('npm info ' + this.name + ' version: ' + this.version)
48+
}
49+
cb(null, this.version)
50+
})
51+
}
52+
53+
addTags (tags) {
54+
tags = Dependency.toArray(tags)
55+
if (tags.length > 0) {
56+
for (const tag of Dependency.toArray(tags)) {
57+
if (this.tags.indexOf(tag) === -1) {
58+
this.tags.push(tag)
59+
}
60+
}
61+
}
62+
return this.tags
63+
}
64+
65+
// is there a tag in list that is in tags, or does list have '*' in it?
66+
isInTags (list) {
67+
if ((this.tags.length === 0) || (this.tags.indexOf('*') > -1)) {
68+
return '*'
69+
}
70+
list = Dependency.toArray(list)
71+
for (var i = 0; i < list.length; i++) {
72+
if (this.tags.indexOf(list[i]) > -1) {
73+
return list[i]
74+
}
75+
}
76+
return null
77+
}
78+
79+
isInstalled () {
80+
try {
81+
const dir = path.join(Dependency.findNodeModules(), this.name)
82+
require.resolve(path.join(dir))
83+
return true
84+
} catch (e) {
85+
return false
86+
}
87+
}
88+
89+
install (options, cb) {
90+
if (typeof options === 'function') {
91+
cb = options
92+
options = {}
93+
}
94+
const nav = this.nameAtVersion
95+
if (options.verbose) {
96+
console.error('npm install ' + nav)
97+
}
98+
const child = spawn('npm', ['install', nav], {stdio: options.stdio})
99+
child.once('error', (er) => {
100+
if (options.verbose) {
101+
console.error('Error installing', er.message)
102+
}
103+
cb()
104+
})
105+
child.once('close', (code) => {
106+
if (code !== 0) {
107+
if (options.verbose) {
108+
console.error('Error installing', nav, 'code', code, 'stdio', options.stdio)
109+
}
110+
}
111+
cb()
112+
})
113+
}
114+
115+
toJSON () {
116+
const ver = this.version || '*'
117+
if (this.tags.length > 0) {
118+
return [ver].concat(this.tags)
119+
}
120+
return ver
121+
}
122+
123+
static toArray (stringOrArray) {
124+
if (!stringOrArray) {
125+
return []
126+
}
127+
if (typeof stringOrArray === 'string') {
128+
return [stringOrArray]
129+
} else if (Array.isArray(stringOrArray)) {
130+
return stringOrArray
131+
}
132+
throw new TypeError('invalid input not string or array' + stringOrArray)
133+
}
134+
135+
// Search up the path, starting at cwd, looking for node_modules
136+
// Note: require works off of the path of *this* module by default, not
137+
// from process.cwd(). Find the correct node_modules directory from cwd,
138+
// and use absolute paths to load from that directory.
139+
static findNodeModules (filename) {
140+
filename = filename || 'node_modules'
141+
if (Dependency.__root) {
142+
return path.join(Dependency.__root, filename)
143+
}
144+
let parts = process.cwd().split(path.sep)
145+
while (parts.length > 1) {
146+
Dependency.__root = path.sep + path.join.apply(path, parts)
147+
if (fs.existsSync(path.join(Dependency.__root, 'package.json'))) {
148+
return path.join(Dependency.__root, filename)
149+
}
150+
parts.pop()
151+
}
152+
Dependency.clear()
153+
throw new Error('Not in a node package hierarchy')
154+
}
155+
156+
// Clear the cache of the project root
157+
static clear () {
158+
delete Dependency.__root
159+
}
160+
}
161+
module.exports = Dependency

0 commit comments

Comments
 (0)