1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
'use strict'
const path = require('path')
const fs = require('graceful-fs')
const BB = require('bluebird')
const gentleFs = require('gentle-fs')
const linkIfExists = BB.promisify(gentleFs.linkIfExists)
const gentleFsBinLink = BB.promisify(gentleFs.binLink)
const open = BB.promisify(fs.open)
const close = BB.promisify(fs.close)
const read = BB.promisify(fs.read, {multiArgs: true})
const chmod = BB.promisify(fs.chmod)
const readFile = BB.promisify(fs.readFile)
const writeFileAtomic = BB.promisify(require('write-file-atomic'))
const normalize = require('npm-normalize-package-bin')
module.exports = BB.promisify(binLinks)
function binLinks (pkg, folder, global, opts, cb) {
pkg = normalize(pkg)
// if it's global, and folder is in {prefix}/node_modules,
// then bins are in {prefix}/bin
// otherwise, then bins are in folder/../.bin
var parent = pkg.name && pkg.name[0] === '@' ? path.dirname(path.dirname(folder)) : path.dirname(folder)
var gnm = global && opts.globalDir
var gtop = parent === gnm
opts.log.info('linkStuff', opts.pkgId)
opts.log.silly('linkStuff', opts.pkgId, 'has', parent, 'as its parent node_modules')
if (global) opts.log.silly('linkStuff', opts.pkgId, 'is part of a global install')
if (gnm) opts.log.silly('linkStuff', opts.pkgId, 'is installed into a global node_modules')
if (gtop) opts.log.silly('linkStuff', opts.pkgId, 'is installed into the top-level global node_modules')
return BB.join(
linkBins(pkg, folder, parent, gtop, opts),
linkMans(pkg, folder, parent, gtop, opts)
).asCallback(cb)
}
function isHashbangFile (file) {
return open(file, 'r').then(fileHandle => {
return read(fileHandle, Buffer.alloc(2), 0, 2, 0).spread((_, buf) => {
if (!hasHashbang(buf)) return []
return read(fileHandle, Buffer.alloc(2048), 0, 2048, 0)
}).spread((_, buf) => buf && hasCR(buf), /* istanbul ignore next */ () => false)
.finally(() => close(fileHandle))
}).catch(/* istanbul ignore next */ () => false)
}
function hasHashbang (buf) {
const str = buf.toString()
return str.slice(0, 2) === '#!'
}
function hasCR (buf) {
return /^#![^\n]+\r\n/.test(buf)
}
function dos2Unix (file) {
return readFile(file, 'utf8').then(content => {
return writeFileAtomic(file, content.replace(/^(#![^\n]+)\r\n/, '$1\n'))
})
}
function getLinkOpts (opts, gently) {
return Object.assign({}, opts, { gently: gently })
}
function linkBins (pkg, folder, parent, gtop, opts) {
if (!pkg.bin || (!gtop && path.basename(parent) !== 'node_modules')) {
return
}
var linkOpts = getLinkOpts(opts, gtop && folder)
var execMode = parseInt('0777', 8) & (~opts.umask)
var binRoot = gtop ? opts.globalBin
: path.resolve(parent, '.bin')
opts.log.verbose('linkBins', [pkg.bin, binRoot, gtop])
return BB.map(Object.keys(pkg.bin), bin => {
var dest = path.resolve(binRoot, bin)
var src = path.resolve(folder, pkg.bin[bin])
/* istanbul ignore if - that unpossible */
if (src.indexOf(folder) !== 0) {
throw new Error('invalid bin entry for package ' +
pkg._id + '. key=' + bin + ', value=' + pkg.bin[bin])
}
return linkBin(src, dest, linkOpts).then(() => {
// bins should always be executable.
// XXX skip chmod on windows?
return chmod(src, execMode)
}).then(() => {
return isHashbangFile(src)
}).then(isHashbang => {
if (!isHashbang) return
opts.log.silly('linkBins', 'Converting line endings of hashbang file:', src)
return dos2Unix(src)
}).then(() => {
if (!gtop) return
var dest = path.resolve(binRoot, bin)
var out = opts.parseable
? dest + '::' + src + ':BINFILE'
: dest + ' -> ' + src
if (!opts.json && !opts.parseable) {
opts.log.clearProgress()
console.log(out)
opts.log.showProgress()
}
}).catch(err => {
/* istanbul ignore next */
if (err.code === 'ENOENT' && opts.ignoreScripts) return
throw err
})
})
}
function linkBin (from, to, opts) {
// do not clobber global bins
if (opts.globalBin && to.indexOf(opts.globalBin) === 0) {
opts.clobberLinkGently = true
}
return gentleFsBinLink(from, to, opts)
}
function linkMans (pkg, folder, parent, gtop, opts) {
if (!pkg.man || !gtop || process.platform === 'win32') return
var manRoot = path.resolve(opts.prefix, 'share', 'man')
opts.log.verbose('linkMans', 'man files are', pkg.man, 'in', manRoot)
// make sure that the mans are unique.
// otherwise, if there are dupes, it'll fail with EEXIST
var set = pkg.man.reduce(function (acc, man) {
if (typeof man !== 'string') {
return acc
}
const cleanMan = path.join('/', man).replace(/\\|:/g, '/').substr(1)
acc[path.basename(man)] = cleanMan
return acc
}, {})
var manpages = pkg.man.filter(function (man) {
if (typeof man !== 'string') {
return false
}
const cleanMan = path.join('/', man).replace(/\\|:/g, '/').substr(1)
return set[path.basename(man)] === cleanMan
})
return BB.map(manpages, man => {
opts.log.silly('linkMans', 'preparing to link', man)
var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
if (!parseMan) {
throw new Error(
man + ' is not a valid name for a man file. ' +
'Man files must end with a number, ' +
'and optionally a .gz suffix if they are compressed.'
)
}
var stem = parseMan[1]
var sxn = parseMan[2]
var bn = path.basename(stem)
var manSrc = path.resolve(folder, man)
/* istanbul ignore if - that unpossible */
if (manSrc.indexOf(folder) !== 0) {
throw new Error('invalid man entry for package ' +
pkg._id + '. man=' + manSrc)
}
var manDest = path.join(manRoot, 'man' + sxn, bn)
// man pages should always be clobbering gently, because they are
// only installed for top-level global packages, so never destroy
// a link if it doesn't point into the folder we're linking
opts.clobberLinkGently = true
return linkIfExists(manSrc, manDest, getLinkOpts(opts, gtop && folder))
})
}
|