index.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. 'use strict'
  2. // Do a two-pass walk, first to get the list of packages that need to be
  3. // bundled, then again to get the actual files and folders.
  4. // Keep a cache of node_modules content and package.json data, so that the
  5. // second walk doesn't have to re-do all the same work.
  6. const bundleWalk = require('npm-bundled')
  7. const BundleWalker = bundleWalk.BundleWalker
  8. const BundleWalkerSync = bundleWalk.BundleWalkerSync
  9. const ignoreWalk = require('ignore-walk')
  10. const IgnoreWalker = ignoreWalk.Walker
  11. const IgnoreWalkerSync = ignoreWalk.WalkerSync
  12. const rootBuiltinRules = Symbol('root-builtin-rules')
  13. const packageNecessaryRules = Symbol('package-necessary-rules')
  14. const path = require('path')
  15. const defaultRules = [
  16. '.npmignore',
  17. '.gitignore',
  18. '**/.git',
  19. '**/.svn',
  20. '**/.hg',
  21. '**/CVS',
  22. '**/.git/**',
  23. '**/.svn/**',
  24. '**/.hg/**',
  25. '**/CVS/**',
  26. '/.lock-wscript',
  27. '/.wafpickle-*',
  28. '/build/config.gypi',
  29. 'npm-debug.log',
  30. '**/.npmrc',
  31. '.*.swp',
  32. '**/.DS_Store/**',
  33. '._*',
  34. '**/._*/**',
  35. '*.orig',
  36. '/package-lock.json',
  37. '/yarn.lock',
  38. 'archived-packages/**',
  39. 'core',
  40. '!core/',
  41. '!**/core/',
  42. '*.core',
  43. '*.vgcore',
  44. 'vgcore.*',
  45. 'core.+([0-9])',
  46. ]
  47. // a decorator that applies our custom rules to an ignore walker
  48. const npmWalker = Class => class Walker extends Class {
  49. constructor (opt) {
  50. opt = opt || {}
  51. // the order in which rules are applied.
  52. opt.ignoreFiles = [
  53. rootBuiltinRules,
  54. 'package.json',
  55. '.npmignore',
  56. '.gitignore',
  57. packageNecessaryRules
  58. ]
  59. opt.includeEmpty = false
  60. opt.path = opt.path || process.cwd()
  61. const dirName = path.basename(opt.path)
  62. const parentName = path.basename(path.dirname(opt.path))
  63. opt.follow =
  64. dirName === 'node_modules' ||
  65. (parentName === 'node_modules' && /^@/.test(dirName))
  66. super(opt)
  67. // ignore a bunch of things by default at the root level.
  68. // also ignore anything in node_modules, except bundled dependencies
  69. if (!this.parent) {
  70. this.bundled = opt.bundled || []
  71. this.bundledScopes = Array.from(new Set(
  72. this.bundled.filter(f => /^@/.test(f))
  73. .map(f => f.split('/')[0])))
  74. const rules = defaultRules.join('\n') + '\n'
  75. this.packageJsonCache = opt.packageJsonCache || new Map()
  76. super.onReadIgnoreFile(rootBuiltinRules, rules, _=>_)
  77. } else {
  78. this.bundled = []
  79. this.bundledScopes = []
  80. this.packageJsonCache = this.parent.packageJsonCache
  81. }
  82. }
  83. filterEntry (entry, partial) {
  84. // get the partial path from the root of the walk
  85. const p = this.path.substr(this.root.length + 1)
  86. const pkgre = /^node_modules\/(@[^\/]+\/?[^\/]+|[^\/]+)(\/.*)?$/
  87. const isRoot = !this.parent
  88. const pkg = isRoot && pkgre.test(entry) ?
  89. entry.replace(pkgre, '$1') : null
  90. const rootNM = isRoot && entry === 'node_modules'
  91. const rootPJ = isRoot && entry === 'package.json'
  92. return (
  93. // if we're in a bundled package, check with the parent.
  94. /^node_modules($|\/)/i.test(p) ? this.parent.filterEntry(
  95. this.basename + '/' + entry, partial)
  96. // if package is bundled, all files included
  97. // also include @scope dirs for bundled scoped deps
  98. // they'll be ignored if no files end up in them.
  99. // However, this only matters if we're in the root.
  100. // node_modules folders elsewhere, like lib/node_modules,
  101. // should be included normally unless ignored.
  102. : pkg ? -1 !== this.bundled.indexOf(pkg) ||
  103. -1 !== this.bundledScopes.indexOf(pkg)
  104. // only walk top node_modules if we want to bundle something
  105. : rootNM ? !!this.bundled.length
  106. // always include package.json at the root.
  107. : rootPJ ? true
  108. // otherwise, follow ignore-walk's logic
  109. : super.filterEntry(entry, partial)
  110. )
  111. }
  112. filterEntries () {
  113. if (this.ignoreRules['package.json'])
  114. this.ignoreRules['.gitignore'] = this.ignoreRules['.npmignore'] = null
  115. else if (this.ignoreRules['.npmignore'])
  116. this.ignoreRules['.gitignore'] = null
  117. this.filterEntries = super.filterEntries
  118. super.filterEntries()
  119. }
  120. addIgnoreFile (file, then) {
  121. const ig = path.resolve(this.path, file)
  122. if (this.packageJsonCache.has(ig))
  123. this.onPackageJson(ig, this.packageJsonCache.get(ig), then)
  124. else
  125. super.addIgnoreFile(file, then)
  126. }
  127. onPackageJson (ig, pkg, then) {
  128. this.packageJsonCache.set(ig, pkg)
  129. // if there's a bin, browser or main, make sure we don't ignore it
  130. // also, don't ignore the package.json itself!
  131. const rules = [
  132. pkg.browser ? '!' + pkg.browser : '',
  133. pkg.main ? '!' + pkg.main : '',
  134. '!package.json',
  135. '!@(readme|copying|license|licence|notice|changes|changelog|history){,.*[^~$]}'
  136. ]
  137. if (pkg.bin)
  138. if (typeof pkg.bin === "object")
  139. for (const key in pkg.bin)
  140. rules.push('!' + pkg.bin[key])
  141. else
  142. rules.push('!' + pkg.bin)
  143. const data = rules.filter(f => f).join('\n') + '\n'
  144. super.onReadIgnoreFile(packageNecessaryRules, data, _=>_)
  145. if (Array.isArray(pkg.files))
  146. super.onReadIgnoreFile('package.json', '*\n' + pkg.files.map(
  147. f => '!' + f + '\n!' + f.replace(/\/+$/, '') + '/**'
  148. ).join('\n') + '\n', then)
  149. else
  150. then()
  151. }
  152. // override parent onstat function to nix all symlinks
  153. onstat (st, entry, file, dir, then) {
  154. if (st.isSymbolicLink())
  155. then()
  156. else
  157. super.onstat(st, entry, file, dir, then)
  158. }
  159. onReadIgnoreFile (file, data, then) {
  160. if (file === 'package.json')
  161. try {
  162. const ig = path.resolve(this.path, file)
  163. this.onPackageJson(ig, JSON.parse(data), then)
  164. } catch (er) {
  165. // ignore package.json files that are not json
  166. then()
  167. }
  168. else
  169. super.onReadIgnoreFile(file, data, then)
  170. }
  171. sort (a, b) {
  172. return sort(a, b)
  173. }
  174. }
  175. class Walker extends npmWalker(IgnoreWalker) {
  176. walker (entry, then) {
  177. new Walker(this.walkerOpt(entry)).on('done', then).start()
  178. }
  179. }
  180. class WalkerSync extends npmWalker(IgnoreWalkerSync) {
  181. walker (entry, then) {
  182. new WalkerSync(this.walkerOpt(entry)).start()
  183. then()
  184. }
  185. }
  186. const walk = (options, callback) => {
  187. options = options || {}
  188. const p = new Promise((resolve, reject) => {
  189. const bw = new BundleWalker(options)
  190. bw.on('done', bundled => {
  191. options.bundled = bundled
  192. options.packageJsonCache = bw.packageJsonCache
  193. new Walker(options).on('done', resolve).on('error', reject).start()
  194. })
  195. bw.start()
  196. })
  197. return callback ? p.then(res => callback(null, res), callback) : p
  198. }
  199. const walkSync = options => {
  200. options = options || {}
  201. const bw = new BundleWalkerSync(options).start()
  202. options.bundled = bw.result
  203. options.packageJsonCache = bw.packageJsonCache
  204. const walker = new WalkerSync(options)
  205. walker.start()
  206. return walker.result
  207. }
  208. // package.json first, node_modules last, files before folders, alphasort
  209. const sort = (a, b) =>
  210. a === 'package.json' ? -1
  211. : b === 'package.json' ? 1
  212. : /^node_modules/.test(a) && !/^node_modules/.test(b) ? 1
  213. : /^node_modules/.test(b) && !/^node_modules/.test(a) ? -1
  214. : path.dirname(a) === '.' && path.dirname(b) !== '.' ? -1
  215. : path.dirname(b) === '.' && path.dirname(a) !== '.' ? 1
  216. : a.localeCompare(b)
  217. module.exports = walk
  218. walk.sync = walkSync
  219. walk.Walker = Walker
  220. walk.WalkerSync = WalkerSync