|
|
|
// npm build command
|
|
|
|
|
|
|
|
// everything about the installation after the creation of
|
|
|
|
// the .npm/{name}/{version}/package folder.
|
|
|
|
// linking the modules into the npm.root,
|
|
|
|
// resolving dependencies, etc.
|
|
|
|
|
|
|
|
// This runs AFTER install or link are completed.
|
|
|
|
|
|
|
|
var npm = require("./npm.js")
|
|
|
|
, log = require("npmlog")
|
|
|
|
, chain = require("slide").chain
|
|
|
|
, fs = require("graceful-fs")
|
|
|
|
, path = require("path")
|
|
|
|
, lifecycle = require("./utils/lifecycle.js")
|
|
|
|
, readJson = require("read-package-json")
|
|
|
|
, link = require("./utils/link.js")
|
|
|
|
, linkIfExists = link.ifExists
|
|
|
|
, cmdShim = require("cmd-shim")
|
|
|
|
, cmdShimIfExists = cmdShim.ifExists
|
|
|
|
, asyncMap = require("slide").asyncMap
|
|
|
|
|
|
|
|
module.exports = build
|
|
|
|
build.usage = "npm build <folder>\n(this is plumbing)"
|
|
|
|
|
|
|
|
build._didBuild = {}
|
|
|
|
build._noLC = {}
|
|
|
|
function build (args, global, didPre, didRB, cb) {
|
|
|
|
if (typeof cb !== "function") cb = didRB, didRB = false
|
|
|
|
if (typeof cb !== "function") cb = didPre, didPre = false
|
|
|
|
if (typeof cb !== "function") {
|
|
|
|
cb = global, global = npm.config.get("global")
|
|
|
|
}
|
|
|
|
// it'd be nice to asyncMap these, but actually, doing them
|
|
|
|
// in parallel generally munges up the output from node-waf
|
|
|
|
var builder = build_(global, didPre, didRB)
|
|
|
|
chain(args.map(function (arg) { return function (cb) {
|
|
|
|
builder(arg, cb)
|
|
|
|
}}), cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
function build_ (global, didPre, didRB) { return function (folder, cb) {
|
|
|
|
folder = path.resolve(folder)
|
|
|
|
build._didBuild[folder] = true
|
|
|
|
log.info("build", folder)
|
|
|
|
readJson(path.resolve(folder, "package.json"), function (er, pkg) {
|
|
|
|
if (er) return cb(er)
|
|
|
|
chain
|
|
|
|
( [ !didPre && [lifecycle, pkg, "preinstall", folder]
|
|
|
|
, [linkStuff, pkg, folder, global, didRB]
|
|
|
|
, pkg.name === "npm" && [writeBuiltinConf, folder]
|
|
|
|
, didPre !== build._noLC && [lifecycle, pkg, "install", folder]
|
|
|
|
, didPre !== build._noLC && [lifecycle, pkg, "postinstall", folder]
|
|
|
|
, didPre !== build._noLC
|
|
|
|
&& npm.config.get("npat")
|
|
|
|
&& [lifecycle, pkg, "test", folder] ]
|
|
|
|
, cb )
|
|
|
|
})
|
|
|
|
}}
|
|
|
|
|
|
|
|
function writeBuiltinConf (folder, cb) {
|
|
|
|
// the builtin config is "sticky". Any time npm installs itself,
|
|
|
|
// it puts its builtin config file there, as well.
|
|
|
|
if (!npm.config.usingBuiltin
|
|
|
|
|| folder !== path.dirname(__dirname)) {
|
|
|
|
return cb()
|
|
|
|
}
|
|
|
|
npm.config.save("builtin", cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkStuff (pkg, folder, global, didRB, cb) {
|
|
|
|
// allow to opt out of linking binaries.
|
|
|
|
if (npm.config.get("bin-links") === false) return cb()
|
|
|
|
|
|
|
|
// 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 = path.dirname(folder)
|
|
|
|
, gnm = global && npm.globalDir
|
|
|
|
, top = parent === npm.dir
|
|
|
|
, gtop = parent === gnm
|
|
|
|
|
|
|
|
log.verbose("linkStuff", [global, gnm, gtop, parent])
|
|
|
|
log.info("linkStuff", pkg._id)
|
|
|
|
|
|
|
|
shouldWarn(pkg, folder, global, function() {
|
|
|
|
asyncMap( [linkBins, linkMans, !didRB && rebuildBundles]
|
|
|
|
, function (fn, cb) {
|
|
|
|
if (!fn) return cb()
|
|
|
|
log.verbose(fn.name, pkg._id)
|
|
|
|
fn(pkg, folder, parent, gtop, cb)
|
|
|
|
}, cb)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function shouldWarn(pkg, folder, global, cb) {
|
|
|
|
var parent = path.dirname(folder)
|
|
|
|
, top = parent === npm.dir
|
|
|
|
, cwd = process.cwd()
|
|
|
|
|
|
|
|
readJson(path.resolve(cwd, "package.json"), function(er, topPkg) {
|
|
|
|
if (er) return cb(er)
|
|
|
|
|
|
|
|
var linkedPkg = path.basename(cwd)
|
|
|
|
, currentPkg = path.basename(folder)
|
|
|
|
|
|
|
|
// current searched package is the linked package on first call
|
|
|
|
if (linkedPkg !== currentPkg) {
|
|
|
|
|
|
|
|
if (!topPkg.dependencies) return cb()
|
|
|
|
|
|
|
|
// don't generate a warning if it's listed in dependencies
|
|
|
|
if (Object.keys(topPkg.dependencies).indexOf(currentPkg) === -1) {
|
|
|
|
|
|
|
|
if (top && pkg.preferGlobal && !global) {
|
|
|
|
log.warn("prefer global", pkg._id + " should be installed with -g")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cb()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function rebuildBundles (pkg, folder, parent, gtop, cb) {
|
|
|
|
if (!npm.config.get("rebuild-bundle")) return cb()
|
|
|
|
|
|
|
|
var deps = Object.keys(pkg.dependencies || {})
|
|
|
|
.concat(Object.keys(pkg.devDependencies || {}))
|
|
|
|
, bundles = pkg.bundleDependencies || pkg.bundledDependencies || []
|
|
|
|
|
|
|
|
fs.readdir(path.resolve(folder, "node_modules"), function (er, files) {
|
|
|
|
// error means no bundles
|
|
|
|
if (er) return cb()
|
|
|
|
|
|
|
|
log.verbose("rebuildBundles", files)
|
|
|
|
// don't asyncMap these, because otherwise build script output
|
|
|
|
// gets interleaved and is impossible to read
|
|
|
|
chain(files.filter(function (file) {
|
|
|
|
// rebuild if:
|
|
|
|
// not a .folder, like .bin or .hooks
|
|
|
|
return !file.match(/^[\._-]/)
|
|
|
|
// not some old 0.x style bundle
|
|
|
|
&& file.indexOf("@") === -1
|
|
|
|
// either not a dep, or explicitly bundled
|
|
|
|
&& (deps.indexOf(file) === -1 || bundles.indexOf(file) !== -1)
|
|
|
|
}).map(function (file) {
|
|
|
|
file = path.resolve(folder, "node_modules", file)
|
|
|
|
return function (cb) {
|
|
|
|
if (build._didBuild[file]) return cb()
|
|
|
|
log.verbose("rebuild bundle", file)
|
|
|
|
// if file is not a package dir, then don't do it.
|
|
|
|
fs.lstat(path.resolve(file, "package.json"), function (er, st) {
|
|
|
|
if (er) return cb()
|
|
|
|
build_(false)(file, cb)
|
|
|
|
})
|
|
|
|
}}), cb)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkBins (pkg, folder, parent, gtop, cb) {
|
|
|
|
if (!pkg.bin || !gtop && path.basename(parent) !== "node_modules") {
|
|
|
|
return cb()
|
|
|
|
}
|
|
|
|
var binRoot = gtop ? npm.globalBin
|
|
|
|
: path.resolve(parent, ".bin")
|
|
|
|
log.verbose("link bins", [pkg.bin, binRoot, gtop])
|
|
|
|
|
|
|
|
asyncMap(Object.keys(pkg.bin), function (b, cb) {
|
|
|
|
linkBin( path.resolve(folder, pkg.bin[b])
|
|
|
|
, path.resolve(binRoot, b)
|
|
|
|
, gtop && folder
|
|
|
|
, function (er) {
|
|
|
|
if (er) return cb(er)
|
|
|
|
// bins should always be executable.
|
|
|
|
// XXX skip chmod on windows?
|
|
|
|
var src = path.resolve(folder, pkg.bin[b])
|
|
|
|
fs.chmod(src, npm.modes.exec, function (er) {
|
|
|
|
if (er && er.code === "ENOENT" && npm.config.get("ignore-scripts")) {
|
|
|
|
return cb()
|
|
|
|
}
|
|
|
|
if (er || !gtop) return cb(er)
|
|
|
|
var dest = path.resolve(binRoot, b)
|
|
|
|
, out = npm.config.get("parseable")
|
|
|
|
? dest + "::" + src + ":BINFILE"
|
|
|
|
: dest + " -> " + src
|
|
|
|
console.log(out)
|
|
|
|
cb()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}, cb)
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkBin (from, to, gently, cb) {
|
|
|
|
if (process.platform !== "win32") {
|
|
|
|
return linkIfExists(from, to, gently, cb)
|
|
|
|
} else {
|
|
|
|
return cmdShimIfExists(from, to, cb)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function linkMans (pkg, folder, parent, gtop, cb) {
|
|
|
|
if (!pkg.man || !gtop || process.platform === "win32") return cb()
|
|
|
|
|
|
|
|
var manRoot = path.resolve(npm.config.get("prefix"), "share", "man")
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
acc[path.basename(man)] = man
|
|
|
|
return acc
|
|
|
|
}, {})
|
|
|
|
pkg.man = pkg.man.filter(function (man) {
|
|
|
|
return set[path.basename(man)] === man
|
|
|
|
})
|
|
|
|
|
|
|
|
asyncMap(pkg.man, function (man, cb) {
|
|
|
|
if (typeof man !== "string") return cb()
|
|
|
|
var parseMan = man.match(/(.*\.([0-9]+)(\.gz)?)$/)
|
|
|
|
, stem = parseMan[1]
|
|
|
|
, sxn = parseMan[2]
|
|
|
|
, gz = parseMan[3] || ""
|
|
|
|
, bn = path.basename(stem)
|
|
|
|
, manDest = path.join(manRoot, "man" + sxn, bn)
|
|
|
|
|
|
|
|
linkIfExists(man, manDest, gtop && folder, cb)
|
|
|
|
}, cb)
|
|
|
|
}
|