get-npmignore.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * @author Toru Nagashima
  3. * @copyright 2016 Toru Nagashima. All rights reserved.
  4. * See LICENSE file in root directory for full license.
  5. */
  6. "use strict"
  7. //------------------------------------------------------------------------------
  8. // Requirements
  9. //------------------------------------------------------------------------------
  10. const fs = require("fs")
  11. const path = require("path")
  12. const ignore = require("ignore")
  13. const Cache = require("./cache")
  14. const exists = require("./exists")
  15. const getPackageJson = require("./get-package-json")
  16. //------------------------------------------------------------------------------
  17. // Helpers
  18. //------------------------------------------------------------------------------
  19. const cache = new Cache()
  20. const SLASH_AT_BEGIN_AND_END = /^!?\/+|^!|\/+$/g
  21. const PARENT_RELATIVE_PATH = /^\.\./
  22. const NEVER_IGNORED = /^(?:readme\.[^.]*|(?:licen[cs]e|changes|changelog|history)(?:\.[^.]*)?)$/i
  23. /**
  24. * Checks whether or not a given file name is a relative path to a ancestor
  25. * directory.
  26. *
  27. * @param {string} filePath - A file name to check.
  28. * @returns {boolean} `true` if the file name is a relative path to a ancestor
  29. * directory.
  30. */
  31. function notAncestorFiles(filePath) {
  32. return PARENT_RELATIVE_PATH.test(filePath)
  33. }
  34. /**
  35. * @param {function} f - A function.
  36. * @param {function} g - A function.
  37. * @returns {function} A logical-and function of `f` and `g`.
  38. */
  39. function and(f, g) {
  40. return (filePath) => f(filePath) && g(filePath)
  41. }
  42. /**
  43. * @param {function} f - A function.
  44. * @param {function} g - A function.
  45. * @param {function|null} h - A function.
  46. * @returns {function} A logical-or function of `f`, `g`, and `h`.
  47. */
  48. function or(f, g, h) {
  49. return (filePath) => f(filePath) || g(filePath) || (h && h(filePath))
  50. }
  51. /**
  52. * @param {function} f - A function.
  53. * @returns {function} A logical-not function of `f`.
  54. */
  55. function not(f) {
  56. return (filePath) => !f(filePath)
  57. }
  58. /**
  59. * Creates a function which checks whether or not a given file is ignoreable.
  60. *
  61. * @param {object} p - An object of package.json.
  62. * @returns {function} A function which checks whether or not a given file is ignoreable.
  63. */
  64. function filterNeverIgnoredFiles(p) {
  65. const basedir = path.dirname(p.filePath)
  66. const mainFilePath = (typeof p.main === "string") ? path.join(basedir, p.main) : null
  67. return (filePath) => (
  68. path.join(basedir, filePath) !== mainFilePath &&
  69. filePath !== "package.json" &&
  70. !NEVER_IGNORED.test(path.relative(basedir, filePath))
  71. )
  72. }
  73. /**
  74. * Creates a function which checks whether or not a given file should be ignored.
  75. *
  76. * @param {string[]|null} files - File names of whitelist.
  77. * @returns {function|null} A function which checks whether or not a given file should be ignored.
  78. */
  79. function parseWhiteList(files) {
  80. if (!files || !Array.isArray(files)) {
  81. return null
  82. }
  83. const ig = ignore()
  84. ig.add("*")
  85. for (const file of files) {
  86. if (typeof file === "string" && file) {
  87. const prefix = file.startsWith("!") ? "" : "!"
  88. const body = file.replace(SLASH_AT_BEGIN_AND_END, "")
  89. ig.add(`${prefix}/${body}`)
  90. ig.add(`${prefix}/${body}/**`)
  91. }
  92. }
  93. return not(ig.createFilter())
  94. }
  95. /**
  96. * Creates a function which checks whether or not a given file should be ignored.
  97. *
  98. * @param {string} basedir - The directory path "package.json" exists.
  99. * @param {boolean} filesFieldExists - `true` if `files` field of `package.json` exists.
  100. * @returns {function|null} A function which checks whether or not a given file should be ignored.
  101. */
  102. function parseNpmignore(basedir, filesFieldExists) {
  103. let filePath = path.join(basedir, ".npmignore")
  104. if (!exists(filePath)) {
  105. if (filesFieldExists) {
  106. return null
  107. }
  108. filePath = path.join(basedir, ".gitignore")
  109. if (!exists(filePath)) {
  110. return null
  111. }
  112. }
  113. const ig = ignore()
  114. ig.add(fs.readFileSync(filePath, "utf8"))
  115. return not(ig.createFilter())
  116. }
  117. //------------------------------------------------------------------------------
  118. // Public Interface
  119. //------------------------------------------------------------------------------
  120. /**
  121. * Gets an object to check whether or not a given path should be ignored.
  122. * The object is created from:
  123. *
  124. * - `files` field of `package.json`
  125. * - `.npmignore`
  126. *
  127. * @param {string} startPath - A file path to lookup.
  128. * @returns {object}
  129. * An object to check whther or not a given path should be ignored.
  130. * The object has a method `match`.
  131. * `match` returns `true` if a given file path should be ignored.
  132. */
  133. module.exports = function getNpmignore(startPath) {
  134. const retv = { match: notAncestorFiles }
  135. const p = getPackageJson(startPath)
  136. if (p) {
  137. const data = cache.get(p.filePath)
  138. if (data) {
  139. return data
  140. }
  141. const filesIgnore = parseWhiteList(p.files)
  142. const npmignoreIgnore = parseNpmignore(path.dirname(p.filePath), Boolean(filesIgnore))
  143. if (filesIgnore && npmignoreIgnore) {
  144. retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, filesIgnore, npmignoreIgnore))
  145. }
  146. else if (filesIgnore) {
  147. retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, filesIgnore))
  148. }
  149. else if (npmignoreIgnore) {
  150. retv.match = and(filterNeverIgnoredFiles(p), or(notAncestorFiles, npmignoreIgnore))
  151. }
  152. cache.set(p.filePath, retv)
  153. }
  154. return retv
  155. }