fileMatcher.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.copyFiles = exports.getFileMatchers = exports.getNodeModuleFileMatcher = exports.getMainFileMatchers = exports.FileMatcher = exports.excludedExts = exports.excludedNames = void 0;
  4. const bluebird_lst_1 = require("bluebird-lst");
  5. const builder_util_1 = require("builder-util");
  6. const fs_1 = require("builder-util/out/fs");
  7. const promises_1 = require("fs/promises");
  8. const minimatch_1 = require("minimatch");
  9. const path = require("path");
  10. const filter_1 = require("./util/filter");
  11. // https://github.com/electron-userland/electron-builder/issues/733
  12. const minimatchOptions = { dot: true };
  13. // noinspection SpellCheckingInspection
  14. exports.excludedNames = ".git,.hg,.svn,CVS,RCS,SCCS," +
  15. "__pycache__,.DS_Store,thumbs.db,.gitignore,.gitkeep,.gitattributes,.npmignore," +
  16. ".idea,.vs,.flowconfig,.jshintrc,.eslintrc,.circleci," +
  17. ".yarn-integrity,.yarn-metadata.json,yarn-error.log,yarn.lock,package-lock.json,npm-debug.log," +
  18. "appveyor.yml,.travis.yml,circle.yml,.nyc_output,.husky,.github";
  19. exports.excludedExts = "iml,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,suo,xproj,cc,d.ts," +
  20. // https://github.com/electron-userland/electron-builder/issues/7512
  21. "mk,a,o,forge-meta";
  22. function ensureNoEndSlash(file) {
  23. if (path.sep !== "/") {
  24. file = file.replace(/\//g, path.sep);
  25. }
  26. if (path.sep !== "\\") {
  27. file = file.replace(/\\/g, path.sep);
  28. }
  29. if (file.endsWith(path.sep)) {
  30. return file.substring(0, file.length - 1);
  31. }
  32. else {
  33. return file;
  34. }
  35. }
  36. /** @internal */
  37. class FileMatcher {
  38. constructor(from, to, macroExpander, patterns) {
  39. this.macroExpander = macroExpander;
  40. this.excludePatterns = null;
  41. this.from = ensureNoEndSlash(macroExpander(from));
  42. this.to = ensureNoEndSlash(macroExpander(to));
  43. this.patterns = (0, builder_util_1.asArray)(patterns).map(it => this.normalizePattern(it));
  44. this.isSpecifiedAsEmptyArray = Array.isArray(patterns) && patterns.length === 0;
  45. }
  46. normalizePattern(pattern) {
  47. if (pattern.startsWith("./")) {
  48. pattern = pattern.substring("./".length);
  49. }
  50. return path.posix.normalize(this.macroExpander(pattern.replace(/\\/g, "/")));
  51. }
  52. addPattern(pattern) {
  53. this.patterns.push(this.normalizePattern(pattern));
  54. }
  55. prependPattern(pattern) {
  56. this.patterns.unshift(this.normalizePattern(pattern));
  57. }
  58. isEmpty() {
  59. return this.patterns.length === 0;
  60. }
  61. containsOnlyIgnore() {
  62. return !this.isEmpty() && this.patterns.find(it => !it.startsWith("!")) == null;
  63. }
  64. computeParsedPatterns(result, fromDir) {
  65. const relativeFrom = fromDir == null ? null : path.relative(fromDir, this.from);
  66. if (this.patterns.length === 0 && relativeFrom != null) {
  67. // file mappings, from here is a file
  68. result.push(new minimatch_1.Minimatch(relativeFrom, minimatchOptions));
  69. return;
  70. }
  71. for (let pattern of this.patterns) {
  72. if (relativeFrom != null) {
  73. pattern = path.join(relativeFrom, pattern);
  74. }
  75. const parsedPattern = new minimatch_1.Minimatch(pattern, minimatchOptions);
  76. result.push(parsedPattern);
  77. // do not add if contains dot (possibly file if has extension)
  78. if (!pattern.includes(".") && !(0, filter_1.hasMagic)(parsedPattern)) {
  79. // https://github.com/electron-userland/electron-builder/issues/545
  80. // add **/*
  81. result.push(new minimatch_1.Minimatch(`${pattern}/**/*`, minimatchOptions));
  82. }
  83. }
  84. }
  85. createFilter() {
  86. const parsedPatterns = [];
  87. this.computeParsedPatterns(parsedPatterns);
  88. return (0, filter_1.createFilter)(this.from, parsedPatterns, this.excludePatterns);
  89. }
  90. toString() {
  91. return `from: ${this.from}, to: ${this.to}, patterns: ${this.patterns.join(", ")}`;
  92. }
  93. }
  94. exports.FileMatcher = FileMatcher;
  95. /** @internal */
  96. function getMainFileMatchers(appDir, destination, macroExpander, platformSpecificBuildOptions, platformPackager, outDir, isElectronCompile) {
  97. const packager = platformPackager.info;
  98. const buildResourceDir = path.resolve(packager.projectDir, packager.buildResourcesDir);
  99. let matchers = packager.isPrepackedAppAsar
  100. ? null
  101. : getFileMatchers(packager.config, "files", destination, {
  102. macroExpander,
  103. customBuildOptions: platformSpecificBuildOptions,
  104. globalOutDir: outDir,
  105. defaultSrc: appDir,
  106. });
  107. if (matchers == null) {
  108. matchers = [new FileMatcher(appDir, destination, macroExpander)];
  109. }
  110. const matcher = matchers[0];
  111. // add default patterns, but only if from equals to app dir
  112. if (matcher.from !== appDir) {
  113. return matchers;
  114. }
  115. // https://github.com/electron-userland/electron-builder/issues/1741#issuecomment-311111418 so, do not use inclusive patterns
  116. const patterns = matcher.patterns;
  117. const customFirstPatterns = [];
  118. // electron-webpack - we need to copy only package.json and node_modules from root dir (and these files are added by default), so, explicit empty array is specified
  119. if (!matcher.isSpecifiedAsEmptyArray && (matcher.isEmpty() || matcher.containsOnlyIgnore())) {
  120. customFirstPatterns.push("**/*");
  121. }
  122. else if (!patterns.includes("package.json")) {
  123. patterns.push("package.json");
  124. }
  125. customFirstPatterns.push("!**/node_modules");
  126. // https://github.com/electron-userland/electron-builder/issues/1482
  127. const relativeBuildResourceDir = path.relative(matcher.from, buildResourceDir);
  128. if (relativeBuildResourceDir.length !== 0 && !relativeBuildResourceDir.startsWith(".")) {
  129. customFirstPatterns.push(`!${relativeBuildResourceDir}{,/**/*}`);
  130. }
  131. const relativeOutDir = matcher.normalizePattern(path.relative(packager.projectDir, outDir));
  132. if (!relativeOutDir.startsWith(".")) {
  133. customFirstPatterns.push(`!${relativeOutDir}{,/**/*}`);
  134. }
  135. // add our default exclusions after last user possibly defined "all"/permissive pattern
  136. let insertIndex = 0;
  137. for (let i = patterns.length - 1; i >= 0; i--) {
  138. if (patterns[i].startsWith("**/")) {
  139. insertIndex = i + 1;
  140. break;
  141. }
  142. }
  143. patterns.splice(insertIndex, 0, ...customFirstPatterns);
  144. patterns.push(`!**/*.{${exports.excludedExts}${packager.config.includePdb === true ? "" : ",pdb"}}`);
  145. patterns.push("!**/._*");
  146. patterns.push("!**/electron-builder.{yaml,yml,json,json5,toml,ts}");
  147. patterns.push(`!**/{${exports.excludedNames}}`);
  148. if (isElectronCompile) {
  149. patterns.push("!.cache{,/**/*}");
  150. }
  151. patterns.push("!.yarn{,/**/*}");
  152. // https://github.com/electron-userland/electron-builder/issues/1969
  153. // exclude ony for app root, use .yarnclean to clean node_modules
  154. patterns.push("!.editorconfig");
  155. patterns.push("!.yarnrc.yml");
  156. const debugLogger = packager.debugLogger;
  157. if (debugLogger.isEnabled) {
  158. //tslint:disable-next-line:no-invalid-template-strings
  159. debugLogger.add(`${macroExpander("${arch}")}.firstOrDefaultFilePatterns`, patterns);
  160. }
  161. return matchers;
  162. }
  163. exports.getMainFileMatchers = getMainFileMatchers;
  164. /** @internal */
  165. function getNodeModuleFileMatcher(appDir, destination, macroExpander, platformSpecificBuildOptions, packager) {
  166. // https://github.com/electron-userland/electron-builder/pull/2948#issuecomment-392241632
  167. // grab only excludes
  168. const matcher = new FileMatcher(appDir, destination, macroExpander);
  169. function addPatterns(patterns) {
  170. if (patterns == null) {
  171. return;
  172. }
  173. else if (!Array.isArray(patterns)) {
  174. if (typeof patterns === "string" && patterns.startsWith("!")) {
  175. matcher.addPattern(patterns);
  176. return;
  177. }
  178. // ignore object form
  179. return;
  180. }
  181. for (const pattern of patterns) {
  182. if (typeof pattern === "string") {
  183. if (pattern.startsWith("!")) {
  184. matcher.addPattern(pattern);
  185. }
  186. }
  187. else {
  188. const fileSet = pattern;
  189. if (fileSet.from == null || fileSet.from === ".") {
  190. for (const p of (0, builder_util_1.asArray)(fileSet.filter)) {
  191. matcher.addPattern(p);
  192. }
  193. }
  194. }
  195. }
  196. }
  197. addPatterns(packager.config.files);
  198. addPatterns(platformSpecificBuildOptions.files);
  199. if (!matcher.isEmpty()) {
  200. matcher.prependPattern("**/*");
  201. }
  202. const debugLogger = packager.debugLogger;
  203. if (debugLogger.isEnabled) {
  204. //tslint:disable-next-line:no-invalid-template-strings
  205. debugLogger.add(`${macroExpander("${arch}")}.nodeModuleFilePatterns`, matcher.patterns);
  206. }
  207. return matcher;
  208. }
  209. exports.getNodeModuleFileMatcher = getNodeModuleFileMatcher;
  210. /** @internal */
  211. function getFileMatchers(config, name, defaultDestination, options) {
  212. const defaultMatcher = new FileMatcher(options.defaultSrc, defaultDestination, options.macroExpander);
  213. const fileMatchers = [];
  214. function addPatterns(patterns) {
  215. if (patterns == null) {
  216. return;
  217. }
  218. else if (!Array.isArray(patterns)) {
  219. if (typeof patterns === "string") {
  220. defaultMatcher.addPattern(patterns);
  221. return;
  222. }
  223. patterns = [patterns];
  224. }
  225. for (const pattern of patterns) {
  226. if (typeof pattern === "string") {
  227. // use normalize to transform ./foo to foo
  228. defaultMatcher.addPattern(pattern);
  229. }
  230. else if (name === "asarUnpack") {
  231. throw new Error(`Advanced file copying not supported for "${name}"`);
  232. }
  233. else {
  234. const from = pattern.from == null ? options.defaultSrc : path.resolve(options.defaultSrc, pattern.from);
  235. const to = pattern.to == null ? defaultDestination : path.resolve(defaultDestination, pattern.to);
  236. fileMatchers.push(new FileMatcher(from, to, options.macroExpander, pattern.filter));
  237. }
  238. }
  239. }
  240. if (name !== "extraDistFiles") {
  241. addPatterns(config[name]);
  242. }
  243. addPatterns(options.customBuildOptions[name]);
  244. if (!defaultMatcher.isEmpty()) {
  245. // default matcher should be first in the array
  246. fileMatchers.unshift(defaultMatcher);
  247. }
  248. // we cannot exclude the whole out dir, because sometimes users want to use some file in the out dir in the patterns
  249. const relativeOutDir = defaultMatcher.normalizePattern(path.relative(options.defaultSrc, options.globalOutDir));
  250. if (!relativeOutDir.startsWith(".")) {
  251. defaultMatcher.addPattern(`!${relativeOutDir}/*-unpacked{,/**/*}`);
  252. }
  253. return fileMatchers.length === 0 ? null : fileMatchers;
  254. }
  255. exports.getFileMatchers = getFileMatchers;
  256. /** @internal */
  257. function copyFiles(matchers, transformer, isUseHardLink) {
  258. if (matchers == null || matchers.length === 0) {
  259. return Promise.resolve();
  260. }
  261. return bluebird_lst_1.default.map(matchers, async (matcher) => {
  262. const fromStat = await (0, fs_1.statOrNull)(matcher.from);
  263. if (fromStat == null) {
  264. builder_util_1.log.warn({ from: matcher.from }, `file source doesn't exist`);
  265. return;
  266. }
  267. if (fromStat.isFile()) {
  268. const toStat = await (0, fs_1.statOrNull)(matcher.to);
  269. // https://github.com/electron-userland/electron-builder/issues/1245
  270. if (toStat != null && toStat.isDirectory()) {
  271. return await (0, fs_1.copyOrLinkFile)(matcher.from, path.join(matcher.to, path.basename(matcher.from)), fromStat, isUseHardLink);
  272. }
  273. await (0, promises_1.mkdir)(path.dirname(matcher.to), { recursive: true });
  274. return await (0, fs_1.copyOrLinkFile)(matcher.from, matcher.to, fromStat);
  275. }
  276. if (matcher.isEmpty() || matcher.containsOnlyIgnore()) {
  277. matcher.prependPattern("**/*");
  278. }
  279. builder_util_1.log.debug({ matcher }, "copying files using pattern");
  280. return await (0, fs_1.copyDir)(matcher.from, matcher.to, { filter: matcher.createFilter(), transformer, isUseHardLink: isUseHardLink ? fs_1.USE_HARD_LINKS : null });
  281. });
  282. }
  283. exports.copyFiles = copyFiles;
  284. //# sourceMappingURL=fileMatcher.js.map