|
|
|
var Ignore = require("fstream-ignore")
|
|
|
|
, inherits = require("inherits")
|
|
|
|
, path = require("path")
|
|
|
|
, fs = require("fs")
|
|
|
|
|
|
|
|
module.exports = Packer
|
|
|
|
|
|
|
|
inherits(Packer, Ignore)
|
|
|
|
|
|
|
|
function Packer (props) {
|
|
|
|
if (!(this instanceof Packer)) {
|
|
|
|
return new Packer(props)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof props === "string") {
|
|
|
|
props = { path: props }
|
|
|
|
}
|
|
|
|
|
|
|
|
props.ignoreFiles = props.ignoreFiles || [ ".npmignore",
|
|
|
|
".gitignore",
|
|
|
|
"package.json" ]
|
|
|
|
|
|
|
|
Ignore.call(this, props)
|
|
|
|
|
|
|
|
this.bundled = props.bundled
|
|
|
|
this.bundleLinks = props.bundleLinks
|
|
|
|
this.package = props.package
|
|
|
|
|
|
|
|
// only do the magic bundling stuff for the node_modules folder that
|
|
|
|
// lives right next to a package.json file.
|
|
|
|
this.bundleMagic = this.parent &&
|
|
|
|
this.parent.packageRoot &&
|
|
|
|
this.basename === "node_modules"
|
|
|
|
|
|
|
|
// in a node_modules folder, resolve symbolic links to
|
|
|
|
// bundled dependencies when creating the package.
|
|
|
|
props.follow = this.follow = this.bundleMagic
|
|
|
|
// console.error("follow?", this.path, props.follow)
|
|
|
|
|
|
|
|
if (this === this.root ||
|
|
|
|
this.parent &&
|
|
|
|
this.parent.bundleMagic &&
|
|
|
|
this.basename.charAt(0) !== ".") {
|
|
|
|
this.readBundledLinks()
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
this.on("entryStat", function (entry, props) {
|
|
|
|
// files should *always* get into tarballs
|
|
|
|
// in a user-writable state, even if they're
|
|
|
|
// being installed from some wackey vm-mounted
|
|
|
|
// read-only filesystem.
|
|
|
|
entry.mode = props.mode = props.mode | 0200
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
Packer.prototype.readBundledLinks = function () {
|
|
|
|
if (this._paused) {
|
|
|
|
this.once("resume", this.addIgnoreFiles)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.pause()
|
|
|
|
fs.readdir(this.path + "/node_modules", function (er, list) {
|
|
|
|
// no harm if there's no bundle
|
|
|
|
var l = list && list.length
|
|
|
|
if (er || l === 0) return this.resume()
|
|
|
|
|
|
|
|
var errState = null
|
|
|
|
, then = function then (er) {
|
|
|
|
if (errState) return
|
|
|
|
if (er) return errState = er, this.resume()
|
|
|
|
if (-- l === 0) return this.resume()
|
|
|
|
}.bind(this)
|
|
|
|
|
|
|
|
list.forEach(function (pkg) {
|
|
|
|
if (pkg.charAt(0) === ".") return then()
|
|
|
|
var pd = this.path + "/node_modules/" + pkg
|
|
|
|
fs.realpath(pd, function (er, rp) {
|
|
|
|
if (er) return then()
|
|
|
|
this.bundleLinks = this.bundleLinks || {}
|
|
|
|
this.bundleLinks[pkg] = rp
|
|
|
|
then()
|
|
|
|
}.bind(this))
|
|
|
|
}, this)
|
|
|
|
}.bind(this))
|
|
|
|
}
|
|
|
|
|
|
|
|
Packer.prototype.applyIgnores = function (entry, partial, entryObj) {
|
|
|
|
// package.json files can never be ignored.
|
|
|
|
if (entry === "package.json") return true
|
|
|
|
|
|
|
|
// readme files should never be ignored.
|
|
|
|
if (entry.match(/^readme(\.[^\.]*)$/i)) return true
|
|
|
|
|
|
|
|
// special rules. see below.
|
|
|
|
if (entry === "node_modules" && this.packageRoot) return true
|
|
|
|
|
|
|
|
// some files are *never* allowed under any circumstances
|
|
|
|
if (entry === ".git" ||
|
|
|
|
entry === ".lock-wscript" ||
|
|
|
|
entry.match(/^\.wafpickle-[0-9]+$/) ||
|
|
|
|
entry === "CVS" ||
|
|
|
|
entry === ".svn" ||
|
|
|
|
entry === ".hg" ||
|
|
|
|
entry.match(/^\..*\.swp$/) ||
|
|
|
|
entry === ".DS_Store" ||
|
|
|
|
entry.match(/^\._/) ||
|
|
|
|
entry === "npm-debug.log"
|
|
|
|
) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// in a node_modules folder, we only include bundled dependencies
|
|
|
|
// also, prevent packages in node_modules from being affected
|
|
|
|
// by rules set in the containing package, so that
|
|
|
|
// bundles don't get busted.
|
|
|
|
// Also, once in a bundle, everything is installed as-is
|
|
|
|
// To prevent infinite cycles in the case of cyclic deps that are
|
|
|
|
// linked with npm link, even in a bundle, deps are only bundled
|
|
|
|
// if they're not already present at a higher level.
|
|
|
|
if (this.bundleMagic) {
|
|
|
|
// bubbling up. stop here and allow anything the bundled pkg allows
|
|
|
|
if (entry.indexOf("/") !== -1) return true
|
|
|
|
|
|
|
|
// never include the .bin. It's typically full of platform-specific
|
|
|
|
// stuff like symlinks and .cmd files anyway.
|
|
|
|
if (entry === ".bin") return false
|
|
|
|
|
|
|
|
var shouldBundle = false
|
|
|
|
// the package root.
|
|
|
|
var p = this.parent
|
|
|
|
// the package before this one.
|
|
|
|
var pp = p && p.parent
|
|
|
|
|
|
|
|
// if this entry has already been bundled, and is a symlink,
|
|
|
|
// and it is the *same* symlink as this one, then exclude it.
|
|
|
|
if (pp && pp.bundleLinks && this.bundleLinks &&
|
|
|
|
pp.bundleLinks[entry] &&
|
|
|
|
pp.bundleLinks[entry] === this.bundleLinks[entry]) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// since it's *not* a symbolic link, if we're *already* in a bundle,
|
|
|
|
// then we should include everything.
|
|
|
|
if (pp && pp.package) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// only include it at this point if it's a bundleDependency
|
|
|
|
var bd = this.package && this.package.bundleDependencies
|
|
|
|
var shouldBundle = bd && bd.indexOf(entry) !== -1
|
|
|
|
// if we're not going to bundle it, then it doesn't count as a bundleLink
|
|
|
|
// if (this.bundleLinks && !shouldBundle) delete this.bundleLinks[entry]
|
|
|
|
return shouldBundle
|
|
|
|
}
|
|
|
|
// if (this.bundled) return true
|
|
|
|
|
|
|
|
return Ignore.prototype.applyIgnores.call(this, entry, partial, entryObj)
|
|
|
|
}
|
|
|
|
|
|
|
|
Packer.prototype.addIgnoreFiles = function () {
|
|
|
|
var entries = this.entries
|
|
|
|
// if there's a .npmignore, then we do *not* want to
|
|
|
|
// read the .gitignore.
|
|
|
|
if (-1 !== entries.indexOf(".npmignore")) {
|
|
|
|
var i = entries.indexOf(".gitignore")
|
|
|
|
if (i !== -1) {
|
|
|
|
entries.splice(i, 1)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.entries = entries
|
|
|
|
|
|
|
|
Ignore.prototype.addIgnoreFiles.call(this)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Packer.prototype.readRules = function (buf, e) {
|
|
|
|
if (e !== "package.json") {
|
|
|
|
return Ignore.prototype.readRules.call(this, buf, e)
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = buf.toString().trim()
|
|
|
|
|
|
|
|
if (buf.length === 0) return []
|
|
|
|
|
|
|
|
try {
|
|
|
|
var p = this.package = JSON.parse(buf)
|
|
|
|
} catch (er) {
|
|
|
|
// just pretend it's a normal old file, not magic at all.
|
|
|
|
return []
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this === this.root) {
|
|
|
|
this.bundleLinks = this.bundleLinks || {}
|
|
|
|
this.bundleLinks[p.name] = this._path
|
|
|
|
}
|
|
|
|
|
|
|
|
this.packageRoot = true
|
|
|
|
this.emit("package", p)
|
|
|
|
|
|
|
|
// make bundle deps predictable
|
|
|
|
if (p.bundledDependencies && !p.bundleDependencies) {
|
|
|
|
p.bundleDependencies = p.bundledDependencies
|
|
|
|
delete p.bundledDependencies
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!p.files || !Array.isArray(p.files)) return []
|
|
|
|
|
|
|
|
// ignore everything except what's in the files array.
|
|
|
|
return ["*"].concat(p.files.map(function (f) {
|
|
|
|
return "!" + f
|
|
|
|
})).concat(p.files.map(function (f) {
|
|
|
|
return "!" + f.replace(/\/+$/, "") + "/**"
|
|
|
|
}))
|
|
|
|
}
|
|
|
|
|
|
|
|
Packer.prototype.getChildProps = function (stat) {
|
|
|
|
var props = Ignore.prototype.getChildProps.call(this, stat)
|
|
|
|
|
|
|
|
props.package = this.package
|
|
|
|
|
|
|
|
props.bundled = this.bundled && this.bundled.slice(0)
|
|
|
|
props.bundleLinks = this.bundleLinks &&
|
|
|
|
Object.create(this.bundleLinks)
|
|
|
|
|
|
|
|
// Directories have to be read as Packers
|
|
|
|
// otherwise fstream.Reader will create a DirReader instead.
|
|
|
|
if (stat.isDirectory()) {
|
|
|
|
props.type = this.constructor
|
|
|
|
}
|
|
|
|
|
|
|
|
// only follow symbolic links directly in the node_modules folder.
|
|
|
|
props.follow = false
|
|
|
|
return props
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var order =
|
|
|
|
[ "package.json"
|
|
|
|
, ".npmignore"
|
|
|
|
, ".gitignore"
|
|
|
|
, /^README(\.md)?$/
|
|
|
|
, "LICENCE"
|
|
|
|
, "LICENSE"
|
|
|
|
, /\.js$/ ]
|
|
|
|
|
|
|
|
Packer.prototype.sort = function (a, b) {
|
|
|
|
for (var i = 0, l = order.length; i < l; i ++) {
|
|
|
|
var o = order[i]
|
|
|
|
if (typeof o === "string") {
|
|
|
|
if (a === o) return -1
|
|
|
|
if (b === o) return 1
|
|
|
|
} else {
|
|
|
|
if (a.match(o)) return -1
|
|
|
|
if (b.match(o)) return 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// deps go in the back
|
|
|
|
if (a === "node_modules") return 1
|
|
|
|
if (b === "node_modules") return -1
|
|
|
|
|
|
|
|
return Ignore.prototype.sort.call(this, a, b)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Packer.prototype.emitEntry = function (entry) {
|
|
|
|
if (this._paused) {
|
|
|
|
this.once("resume", this.emitEntry.bind(this, entry))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// if there is a .gitignore, then we're going to
|
|
|
|
// rename it to .npmignore in the output.
|
|
|
|
if (entry.basename === ".gitignore") {
|
|
|
|
entry.basename = ".npmignore"
|
|
|
|
entry.path = path.resolve(entry.dirname, entry.basename)
|
|
|
|
}
|
|
|
|
|
|
|
|
// all *.gyp files are renamed to binding.gyp for node-gyp
|
|
|
|
// but only when they are in the same folder as a package.json file.
|
|
|
|
if (entry.basename.match(/\.gyp$/) &&
|
|
|
|
this.entries.indexOf("package.json") !== -1) {
|
|
|
|
entry.basename = "binding.gyp"
|
|
|
|
entry.path = path.resolve(entry.dirname, entry.basename)
|
|
|
|
}
|
|
|
|
|
|
|
|
// skip over symbolic links
|
|
|
|
if (entry.type === "SymbolicLink") {
|
|
|
|
entry.abort()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if (entry.type !== "Directory") {
|
|
|
|
// make it so that the folder in the tarball is named "package"
|
|
|
|
var h = path.dirname((entry.root || entry).path)
|
|
|
|
, t = entry.path.substr(h.length + 1).replace(/^[^\/\\]+/, "package")
|
|
|
|
, p = h + "/" + t
|
|
|
|
|
|
|
|
entry.path = p
|
|
|
|
entry.dirname = path.dirname(p)
|
|
|
|
return Ignore.prototype.emitEntry.call(this, entry)
|
|
|
|
}
|
|
|
|
|
|
|
|
// we don't want empty directories to show up in package
|
|
|
|
// tarballs.
|
|
|
|
// don't emit entry events for dirs, but still walk through
|
|
|
|
// and read them. This means that we need to proxy up their
|
|
|
|
// entry events so that those entries won't be missed, since
|
|
|
|
// .pipe() doesn't do anythign special with "child" events, on
|
|
|
|
// with "entry" events.
|
|
|
|
var me = this
|
|
|
|
entry.on("entry", function (e) {
|
|
|
|
if (e.parent === entry) {
|
|
|
|
e.parent = me
|
|
|
|
me.emit("entry", e)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
entry.on("package", this.emit.bind(this, "package"))
|
|
|
|
}
|