1 var Ignore = require('fstream-ignore')
2 var inherits = require('inherits')
3 var path = require('path')
6 module.exports = Packer
8 inherits(Packer, Ignore)
10 function Packer (props) {
11 if (!(this instanceof Packer)) {
12 return new Packer(props)
15 if (typeof props === 'string') {
16 props = { path: props }
19 props.ignoreFiles = props.ignoreFiles || [ '.npmignore',
23 Ignore.call(this, props)
25 this.bundled = props.bundled
26 this.bundleLinks = props.bundleLinks
27 this.package = props.package
29 // only do the magic bundling stuff for the node_modules folder that
30 // lives right next to a package.json file.
31 this.bundleMagic = this.parent &&
32 this.parent.packageRoot &&
33 this.basename === 'node_modules'
35 // in a node_modules folder, resolve symbolic links to
36 // bundled dependencies when creating the package.
37 props.follow = this.follow = this.bundleMagic
38 // console.error("follow?", this.path, props.follow)
40 if (this === this.root ||
42 this.parent.bundleMagic &&
43 this.basename.charAt(0) !== '.') {
44 this.readBundledLinks()
47 this.on('entryStat', function (entry, props) {
48 // files should *always* get into tarballs
49 // in a user-writable state, even if they're
50 // being installed from some wackey vm-mounted
51 // read-only filesystem.
52 entry.mode = props.mode = props.mode | parseInt('0200', 8)
56 Packer.prototype.readBundledLinks = function () {
58 this.once('resume', this.addIgnoreFiles)
63 fs.readdir(this.path + '/node_modules', function (er, list) {
64 // no harm if there's no bundle
65 var l = list && list.length
66 if (er || l === 0) return this.resume()
69 var then = function then (er) {
75 if (--l === 0) return this.resume()
78 list.forEach(function (pkg) {
79 if (pkg.charAt(0) === '.') return then()
80 var pd = this.path + '/node_modules/' + pkg
83 if (pkg.charAt(0) === '@') {
84 fs.readdir(pd, function (er, slist) {
85 var sl = slist && slist.length
86 if (er || sl === 0) return then(er)
89 slist.forEach(function (spkg) {
90 if (spkg.charAt(0) === '.') return then()
91 var spd = pd + '/' + spkg
92 fs.realpath(spd, function (er, rp) {
94 this.bundleLinks = this.bundleLinks || {}
95 this.bundleLinks[pkg + '/' + spkg] = rp
104 fs.realpath(pd, function (er, rp) {
105 if (er) return then()
106 this.bundleLinks = this.bundleLinks || {}
107 this.bundleLinks[pkg] = rp
114 Packer.prototype.applyIgnores = function (entry, partial, entryObj) {
115 if (!entryObj || entryObj.type !== 'Directory') {
116 // package.json files can never be ignored.
117 if (entry === 'package.json') return true
119 // readme files should never be ignored.
120 if (entry.match(/^readme(\.[^\.]*)$/i)) return true
122 // license files should never be ignored.
123 if (entry.match(/^(license|licence)(\.[^\.]*)?$/i)) return true
125 // copyright notice files should never be ignored.
126 if (entry.match(/^(notice)(\.[^\.]*)?$/i)) return true
128 // changelogs should never be ignored.
129 if (entry.match(/^(changes|changelog|history)(\.[^\.]*)?$/i)) return true
132 // special rules. see below.
133 if (entry === 'node_modules' && this.packageRoot) return true
135 // package.json main file should never be ignored.
136 var mainFile = this.package && this.package.main
137 if (mainFile && path.resolve(this.path, entry) === path.resolve(this.path, mainFile)) return true
139 // some files are *never* allowed under any circumstances
140 // (VCS folders, native build cruft, npm cruft, regular cruft)
141 if (entry === '.git' ||
145 entry === '.lock-wscript' ||
146 entry.match(/^\.wafpickle-[0-9]+$/) ||
147 (this.parent && this.parent.packageRoot && this.basename === 'build' &&
148 entry === 'config.gypi') ||
149 entry === 'npm-debug.log' ||
150 entry === '.npmrc' ||
151 entry.match(/^\..*\.swp$/) ||
152 entry === '.DS_Store' ||
158 // in a node_modules folder, we only include bundled dependencies
159 // also, prevent packages in node_modules from being affected
160 // by rules set in the containing package, so that
161 // bundles don't get busted.
162 // Also, once in a bundle, everything is installed as-is
163 // To prevent infinite cycles in the case of cyclic deps that are
164 // linked with npm link, even in a bundle, deps are only bundled
165 // if they're not already present at a higher level.
166 if (this.bundleMagic) {
167 if (entry.charAt(0) === '@') {
168 var firstSlash = entry.indexOf('/')
169 // continue to list the packages in this scope
170 if (firstSlash === -1) return true
172 // bubbling up. stop here and allow anything the bundled pkg allows
173 if (entry.indexOf('/', firstSlash + 1) !== -1) return true
175 // bubbling up. stop here and allow anything the bundled pkg allows
176 else if (entry.indexOf('/') !== -1) return true
178 // never include the .bin. It's typically full of platform-specific
179 // stuff like symlinks and .cmd files anyway.
180 if (entry === '.bin') return false
184 // the package before this one.
185 var pp = p && p.parent
187 // if this entry has already been bundled, and is a symlink,
188 // and it is the *same* symlink as this one, then exclude it.
189 if (pp && pp.bundleLinks && this.bundleLinks &&
190 pp.bundleLinks[entry] &&
191 pp.bundleLinks[entry] === this.bundleLinks[entry]) {
195 // since it's *not* a symbolic link, if we're *already* in a bundle,
196 // then we should include everything.
197 if (pp && pp.package && pp.basename === 'node_modules') {
201 // only include it at this point if it's a bundleDependency
202 var bd = this.package && this.package.bundleDependencies
204 if (bd && !Array.isArray(bd)) {
205 throw new Error(this.package.name + '\'s `bundledDependencies` should ' +
209 var shouldBundle = bd && bd.indexOf(entry) !== -1
210 // if we're not going to bundle it, then it doesn't count as a bundleLink
211 // if (this.bundleLinks && !shouldBundle) delete this.bundleLinks[entry]
214 // if (this.bundled) return true
216 return Ignore.prototype.applyIgnores.call(this, entry, partial, entryObj)
219 Packer.prototype.addIgnoreFiles = function () {
220 var entries = this.entries
221 // if there's a .npmignore, then we do *not* want to
222 // read the .gitignore.
223 if (entries.indexOf('.npmignore') !== -1) {
224 var i = entries.indexOf('.gitignore')
230 this.entries = entries
232 Ignore.prototype.addIgnoreFiles.call(this)
235 Packer.prototype.readRules = function (buf, e) {
236 if (e !== 'package.json') {
237 return Ignore.prototype.readRules.call(this, buf, e)
240 buf = buf.toString().trim()
242 if (buf.length === 0) return []
245 var p = this.package = JSON.parse(buf)
247 // just pretend it's a normal old file, not magic at all.
251 if (this === this.root) {
252 this.bundleLinks = this.bundleLinks || {}
253 this.bundleLinks[p.name] = this._path
256 this.packageRoot = true
257 this.emit('package', p)
259 // make bundle deps predictable
260 if (p.bundledDependencies && !p.bundleDependencies) {
261 p.bundleDependencies = p.bundledDependencies
262 delete p.bundledDependencies
265 if (!p.files || !Array.isArray(p.files)) return []
267 // ignore everything except what's in the files array.
268 return ['*'].concat(p.files.map(function (f) {
270 })).concat(p.files.map(function (f) {
271 return '!' + f.replace(/\/+$/, '') + '/**'
275 Packer.prototype.getChildProps = function (stat) {
276 var props = Ignore.prototype.getChildProps.call(this, stat)
278 props.package = this.package
280 props.bundled = this.bundled && this.bundled.slice(0)
281 props.bundleLinks = this.bundleLinks &&
282 Object.create(this.bundleLinks)
284 // Directories have to be read as Packers
285 // otherwise fstream.Reader will create a DirReader instead.
286 if (stat.isDirectory()) {
287 props.type = this.constructor
290 // only follow symbolic links directly in the node_modules folder.
305 Packer.prototype.sort = function (a, b) {
306 for (var i = 0, l = order.length; i < l; i++) {
308 if (typeof o === 'string') {
309 if (a === o) return -1
310 if (b === o) return 1
312 if (a.match(o)) return -1
313 if (b.match(o)) return 1
317 // deps go in the back
318 if (a === 'node_modules') return 1
319 if (b === 'node_modules') return -1
321 return Ignore.prototype.sort.call(this, a, b)
324 Packer.prototype.emitEntry = function (entry) {
326 this.once('resume', this.emitEntry.bind(this, entry))
330 // if there is a .gitignore, then we're going to
331 // rename it to .npmignore in the output.
332 if (entry.basename === '.gitignore') {
333 entry.basename = '.npmignore'
334 entry.path = path.resolve(entry.dirname, entry.basename)
337 // all *.gyp files are renamed to binding.gyp for node-gyp
338 // but only when they are in the same folder as a package.json file.
339 if (entry.basename.match(/\.gyp$/) &&
340 this.entries.indexOf('package.json') !== -1) {
341 entry.basename = 'binding.gyp'
342 entry.path = path.resolve(entry.dirname, entry.basename)
345 // skip over symbolic links
346 if (entry.type === 'SymbolicLink') {
351 if (entry.type !== 'Directory') {
352 // make it so that the folder in the tarball is named "package"
353 var h = path.dirname((entry.root || entry).path)
354 var t = entry.path.substr(h.length + 1).replace(/^[^\/\\]+/, 'package')
358 entry.dirname = path.dirname(p)
359 return Ignore.prototype.emitEntry.call(this, entry)
362 // we don't want empty directories to show up in package
364 // don't emit entry events for dirs, but still walk through
365 // and read them. This means that we need to proxy up their
366 // entry events so that those entries won't be missed, since
367 // .pipe() doesn't do anythign special with "child" events, on
368 // with "entry" events.
370 entry.on('entry', function (e) {
371 if (e.parent === entry) {
376 entry.on('package', this.emit.bind(this, 'package'))