|
| 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