UNPKG

7.4 kBJavaScriptView Raw
1// npm build command
2
3// everything about the installation after the creation of
4// the .npm/{name}/{version}/package folder.
5// linking the modules into the npm.root,
6// resolving dependencies, etc.
7
8// This runs AFTER install or link are completed.
9
10var npm = require("./npm.js")
11 , log = require("npmlog")
12 , chain = require("slide").chain
13 , fs = require("graceful-fs")
14 , path = require("path")
15 , lifecycle = require("./utils/lifecycle.js")
16 , readJson = require("read-package-json")
17 , link = require("./utils/link.js")
18 , linkIfExists = link.ifExists
19 , cmdShim = require("cmd-shim")
20 , cmdShimIfExists = cmdShim.ifExists
21 , asyncMap = require("slide").asyncMap
22
23module.exports = build
24build.usage = "npm build <folder>\n(this is plumbing)"
25
26build._didBuild = {}
27build._noLC = {}
28function build (args, global, didPre, didRB, cb) {
29 if (typeof cb !== "function") cb = didRB, didRB = false
30 if (typeof cb !== "function") cb = didPre, didPre = false
31 if (typeof cb !== "function") {
32 cb = global, global = npm.config.get("global")
33 }
34 // it'd be nice to asyncMap these, but actually, doing them
35 // in parallel generally munges up the output from node-waf
36 var builder = build_(global, didPre, didRB)
37 chain(args.map(function (arg) { return function (cb) {
38 builder(arg, cb)
39 }}), cb)
40}
41
42function build_ (global, didPre, didRB) { return function (folder, cb) {
43 folder = path.resolve(folder)
44 build._didBuild[folder] = true
45 log.info("build", folder)
46 readJson(path.resolve(folder, "package.json"), function (er, pkg) {
47 if (er) return cb(er)
48 chain
49 ( [ !didPre && [lifecycle, pkg, "preinstall", folder]
50 , [linkStuff, pkg, folder, global, didRB]
51 , pkg.name === "npm" && [writeBuiltinConf, folder]
52 , didPre !== build._noLC && [lifecycle, pkg, "install", folder]
53 , didPre !== build._noLC && [lifecycle, pkg, "postinstall", folder]
54 , didPre !== build._noLC
55 && npm.config.get("npat")
56 && [lifecycle, pkg, "test", folder] ]
57 , cb )
58 })
59}}
60
61function writeBuiltinConf (folder, cb) {
62 // the builtin config is "sticky". Any time npm installs itself,
63 // it puts its builtin config file there, as well.
64 if (!npm.config.usingBuiltin
65 || folder !== path.dirname(__dirname)) {
66 return cb()
67 }
68 npm.config.save("builtin", cb)
69}
70
71function linkStuff (pkg, folder, global, didRB, cb) {
72 // allow to opt out of linking binaries.
73 if (npm.config.get("bin-links") === false) return cb()
74
75 // if it's global, and folder is in {prefix}/node_modules,
76 // then bins are in {prefix}/bin
77 // otherwise, then bins are in folder/../.bin
78 var parent = path.dirname(folder)
79 , gnm = global && npm.globalDir
80 , top = parent === npm.dir
81 , gtop = parent === gnm
82
83 log.verbose("linkStuff", [global, gnm, gtop, parent])
84 log.info("linkStuff", pkg._id)
85
86 shouldWarn(pkg, folder, global, function() {
87 asyncMap( [linkBins, linkMans, !didRB && rebuildBundles]
88 , function (fn, cb) {
89 if (!fn) return cb()
90 log.verbose(fn.name, pkg._id)
91 fn(pkg, folder, parent, gtop, cb)
92 }, cb)
93 })
94}
95
96function shouldWarn(pkg, folder, global, cb) {
97 var parent = path.dirname(folder)
98 , top = parent === npm.dir
99 , cwd = process.cwd()
100
101 readJson(path.resolve(cwd, "package.json"), function(er, topPkg) {
102 if (er) return cb(er)
103
104 var linkedPkg = path.basename(cwd)
105 , currentPkg = path.basename(folder)
106
107 // current searched package is the linked package on first call
108 if (linkedPkg !== currentPkg) {
109
110 if (!topPkg.dependencies) return cb()
111
112 // don't generate a warning if it's listed in dependencies
113 if (Object.keys(topPkg.dependencies).indexOf(currentPkg) === -1) {
114
115 if (top && pkg.preferGlobal && !global) {
116 log.warn("prefer global", pkg._id + " should be installed with -g")
117 }
118 }
119 }
120
121 cb()
122 })
123}
124
125function rebuildBundles (pkg, folder, parent, gtop, cb) {
126 if (!npm.config.get("rebuild-bundle")) return cb()
127
128 var deps = Object.keys(pkg.dependencies || {})
129 .concat(Object.keys(pkg.devDependencies || {}))
130 , bundles = pkg.bundleDependencies || pkg.bundledDependencies || []
131
132 fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
133 // error means no bundles
134 if (er) return cb()
135
136 log.verbose("rebuildBundles", files)
137 // don't asyncMap these, because otherwise build script output
138 // gets interleaved and is impossible to read
139 chain(files.filter(function (file) {
140 // rebuild if:
141 // not a .folder, like .bin or .hooks
142 return !file.match(/^[\._-]/)
143 // not some old 0.x style bundle
144 && file.indexOf("@") === -1
145 // either not a dep, or explicitly bundled
146 && (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
147 }).map(function (file) {
148 file = path.resolve(folder, "node_modules", file)
149 return function (cb) {
150 if (build._didBuild[file]) return cb()
151 log.verbose("rebuild bundle", file)
152 // if file is not a package dir, then don't do it.
153 fs.lstat(path.resolve(file, "package.json"), function (er, st) {
154 if (er) return cb()
155 build_(false)(file, cb)
156 })
157 }}), cb)
158 })
159}
160
161function linkBins (pkg, folder, parent, gtop, cb) {
162 if (!pkg.bin || !gtop && path.basename(parent) !== "node_modules") {
163 return cb()
164 }
165 var binRoot = gtop ? npm.globalBin
166 : path.resolve(parent, ".bin")
167 log.verbose("link bins", [pkg.bin, binRoot, gtop])
168
169 asyncMap(Object.keys(pkg.bin), function (b, cb) {
170 linkBin( path.resolve(folder, pkg.bin[b])
171 , path.resolve(binRoot, b)
172 , gtop && folder
173 , function (er) {
174 if (er) return cb(er)
175 // bins should always be executable.
176 // XXX skip chmod on windows?
177 var src = path.resolve(folder, pkg.bin[b])
178 fs.chmod(src, npm.modes.exec, function (er) {
179 if (er && er.code === "ENOENT" && npm.config.get("ignore-scripts")) {
180 return cb()
181 }
182 if (er || !gtop) return cb(er)
183 var dest = path.resolve(binRoot, b)
184 , out = npm.config.get("parseable")
185 ? dest + "::" + src + ":BINFILE"
186 : dest + " -> " + src
187 console.log(out)
188 cb()
189 })
190 })
191 }, cb)
192}
193
194function linkBin (from, to, gently, cb) {
195 if (process.platform !== "win32") {
196 return linkIfExists(from, to, gently, cb)
197 } else {
198 return cmdShimIfExists(from, to, cb)
199 }
200}
201
202function linkMans (pkg, folder, parent, gtop, cb) {
203 if (!pkg.man || !gtop || process.platform === "win32") return cb()
204
205 var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
206
207 // make sure that the mans are unique.
208 // otherwise, if there are dupes, it'll fail with EEXIST
209 var set = pkg.man.reduce(function (acc, man) {
210 acc[path.basename(man)] = man
211 return acc
212 }, {})
213 pkg.man = pkg.man.filter(function (man) {
214 return set[path.basename(man)] === man
215 })
216
217 asyncMap(pkg.man, function (man, cb) {
218 if (typeof man !== "string") return cb()
219 var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
220 , stem = parseMan[1]
221 , sxn = parseMan[2]
222 , gz = parseMan[3] || ""
223 , bn = path.basename(stem)
224 , manDest = path.join(manRoot, "man" + sxn, bn)
225
226 linkIfExists(man, manDest, gtop && folder, cb)
227 }, cb)
228}