asarUtil.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.AsarPackager = void 0;
  4. const builder_util_1 = require("builder-util");
  5. const fs_1 = require("builder-util/out/fs");
  6. const fs_2 = require("fs");
  7. const promises_1 = require("fs/promises");
  8. const path = require("path");
  9. const appFileCopier_1 = require("../util/appFileCopier");
  10. const asar_1 = require("./asar");
  11. const integrity_1 = require("./integrity");
  12. const unpackDetector_1 = require("./unpackDetector");
  13. // eslint-disable-next-line @typescript-eslint/no-var-requires
  14. const pickle = require("chromium-pickle-js");
  15. /** @internal */
  16. class AsarPackager {
  17. constructor(src, destination, options, unpackPattern) {
  18. this.src = src;
  19. this.destination = destination;
  20. this.options = options;
  21. this.unpackPattern = unpackPattern;
  22. this.fs = new asar_1.AsarFilesystem(this.src);
  23. this.outFile = path.join(destination, "app.asar");
  24. this.unpackedDest = `${this.outFile}.unpacked`;
  25. }
  26. // sort files to minimize file change (i.e. asar file is not changed dramatically on small change)
  27. async pack(fileSets, packager) {
  28. if (this.options.ordering != null) {
  29. // ordering doesn't support transformed files, but ordering is not used functionality - wait user report to fix it
  30. await order(fileSets[0].files, this.options.ordering, fileSets[0].src);
  31. }
  32. await (0, promises_1.mkdir)(path.dirname(this.outFile), { recursive: true });
  33. const unpackedFileIndexMap = new Map();
  34. for (const fileSet of fileSets) {
  35. unpackedFileIndexMap.set(fileSet, await this.createPackageFromFiles(fileSet, packager.info));
  36. }
  37. await this.writeAsarFile(fileSets, unpackedFileIndexMap);
  38. }
  39. async createPackageFromFiles(fileSet, packager) {
  40. const metadata = fileSet.metadata;
  41. // search auto unpacked dir
  42. const unpackedDirs = new Set();
  43. const rootForAppFilesWithoutAsar = path.join(this.destination, "app");
  44. if (this.options.smartUnpack !== false) {
  45. await (0, unpackDetector_1.detectUnpackedDirs)(fileSet, unpackedDirs, this.unpackedDest, rootForAppFilesWithoutAsar);
  46. }
  47. const dirToCreateForUnpackedFiles = new Set(unpackedDirs);
  48. const correctDirNodeUnpackedFlag = async (filePathInArchive, dirNode) => {
  49. for (const dir of unpackedDirs) {
  50. if (filePathInArchive.length > dir.length + 2 && filePathInArchive[dir.length] === path.sep && filePathInArchive.startsWith(dir)) {
  51. dirNode.unpacked = true;
  52. unpackedDirs.add(filePathInArchive);
  53. // not all dirs marked as unpacked after first iteration - because node module dir can be marked as unpacked after processing node module dir content
  54. // e.g. node-notifier/example/advanced.js processed, but only on process vendor/terminal-notifier.app module will be marked as unpacked
  55. await (0, promises_1.mkdir)(path.join(this.unpackedDest, filePathInArchive), { recursive: true });
  56. break;
  57. }
  58. }
  59. };
  60. const transformedFiles = fileSet.transformedFiles;
  61. const taskManager = new builder_util_1.AsyncTaskManager(packager.cancellationToken);
  62. const fileCopier = new fs_1.FileCopier();
  63. let currentDirNode = null;
  64. let currentDirPath = null;
  65. const unpackedFileIndexSet = new Set();
  66. for (let i = 0, n = fileSet.files.length; i < n; i++) {
  67. const file = fileSet.files[i];
  68. const stat = metadata.get(file);
  69. if (stat == null) {
  70. continue;
  71. }
  72. const pathInArchive = path.relative(rootForAppFilesWithoutAsar, (0, appFileCopier_1.getDestinationPath)(file, fileSet));
  73. if (stat.isSymbolicLink()) {
  74. const s = stat;
  75. this.fs.getOrCreateNode(pathInArchive).link = s.relativeLink;
  76. s.pathInArchive = pathInArchive;
  77. unpackedFileIndexSet.add(i);
  78. continue;
  79. }
  80. let fileParent = path.dirname(pathInArchive);
  81. if (fileParent === ".") {
  82. fileParent = "";
  83. }
  84. if (currentDirPath !== fileParent) {
  85. if (fileParent.startsWith("..")) {
  86. throw new Error(`Internal error: path must not start with "..": ${fileParent}`);
  87. }
  88. currentDirPath = fileParent;
  89. currentDirNode = this.fs.getOrCreateNode(fileParent);
  90. // do not check for root
  91. if (fileParent !== "" && !currentDirNode.unpacked) {
  92. if (unpackedDirs.has(fileParent)) {
  93. currentDirNode.unpacked = true;
  94. }
  95. else {
  96. await correctDirNodeUnpackedFlag(fileParent, currentDirNode);
  97. }
  98. }
  99. }
  100. const dirNode = currentDirNode;
  101. const newData = transformedFiles == null ? undefined : transformedFiles.get(i);
  102. const isUnpacked = dirNode.unpacked || (this.unpackPattern != null && this.unpackPattern(file, stat));
  103. const integrity = newData === undefined ? await (0, integrity_1.hashFile)(file) : (0, integrity_1.hashFileContents)(newData);
  104. this.fs.addFileNode(file, dirNode, newData == undefined ? stat.size : Buffer.byteLength(newData), isUnpacked, stat, integrity);
  105. if (isUnpacked) {
  106. if (!dirNode.unpacked && !dirToCreateForUnpackedFiles.has(fileParent)) {
  107. dirToCreateForUnpackedFiles.add(fileParent);
  108. await (0, promises_1.mkdir)(path.join(this.unpackedDest, fileParent), { recursive: true });
  109. }
  110. const unpackedFile = path.join(this.unpackedDest, pathInArchive);
  111. taskManager.addTask(copyFileOrData(fileCopier, newData, file, unpackedFile, stat));
  112. if (taskManager.tasks.length > fs_1.MAX_FILE_REQUESTS) {
  113. await taskManager.awaitTasks();
  114. }
  115. unpackedFileIndexSet.add(i);
  116. }
  117. }
  118. if (taskManager.tasks.length > 0) {
  119. await taskManager.awaitTasks();
  120. }
  121. return unpackedFileIndexSet;
  122. }
  123. writeAsarFile(fileSets, unpackedFileIndexMap) {
  124. return new Promise((resolve, reject) => {
  125. const headerPickle = pickle.createEmpty();
  126. headerPickle.writeString(JSON.stringify(this.fs.header));
  127. const headerBuf = headerPickle.toBuffer();
  128. const sizePickle = pickle.createEmpty();
  129. sizePickle.writeUInt32(headerBuf.length);
  130. const sizeBuf = sizePickle.toBuffer();
  131. const writeStream = (0, fs_2.createWriteStream)(this.outFile);
  132. writeStream.on("error", reject);
  133. writeStream.on("close", resolve);
  134. writeStream.write(sizeBuf);
  135. let fileSetIndex = 0;
  136. let files = fileSets[0].files;
  137. let metadata = fileSets[0].metadata;
  138. let transformedFiles = fileSets[0].transformedFiles;
  139. let unpackedFileIndexSet = unpackedFileIndexMap.get(fileSets[0]);
  140. const w = (index) => {
  141. while (true) {
  142. if (index >= files.length) {
  143. if (++fileSetIndex >= fileSets.length) {
  144. writeStream.end();
  145. return;
  146. }
  147. else {
  148. files = fileSets[fileSetIndex].files;
  149. metadata = fileSets[fileSetIndex].metadata;
  150. transformedFiles = fileSets[fileSetIndex].transformedFiles;
  151. unpackedFileIndexSet = unpackedFileIndexMap.get(fileSets[fileSetIndex]);
  152. index = 0;
  153. }
  154. }
  155. if (!unpackedFileIndexSet.has(index)) {
  156. break;
  157. }
  158. else {
  159. const stat = metadata.get(files[index]);
  160. if (stat != null && stat.isSymbolicLink()) {
  161. (0, fs_2.symlink)(stat.linkRelativeToFile, path.join(this.unpackedDest, stat.pathInArchive), () => w(index + 1));
  162. return;
  163. }
  164. }
  165. index++;
  166. }
  167. const data = transformedFiles == null ? null : transformedFiles.get(index);
  168. const file = files[index];
  169. if (data !== null && data !== undefined) {
  170. writeStream.write(data, () => w(index + 1));
  171. return;
  172. }
  173. // https://github.com/yarnpkg/yarn/pull/3539
  174. const stat = metadata.get(file);
  175. if (stat != null && stat.size < 2 * 1024 * 1024) {
  176. (0, promises_1.readFile)(file)
  177. .then(it => {
  178. writeStream.write(it, () => w(index + 1));
  179. })
  180. .catch((e) => reject(`Cannot read file ${file}: ${e.stack || e}`));
  181. }
  182. else {
  183. const readStream = (0, fs_2.createReadStream)(file);
  184. readStream.on("error", reject);
  185. readStream.once("end", () => w(index + 1));
  186. readStream.on("open", () => {
  187. readStream.pipe(writeStream, {
  188. end: false,
  189. });
  190. });
  191. }
  192. };
  193. writeStream.write(headerBuf, () => w(0));
  194. });
  195. }
  196. }
  197. exports.AsarPackager = AsarPackager;
  198. async function order(filenames, orderingFile, src) {
  199. const orderingFiles = (await (0, promises_1.readFile)(orderingFile, "utf8")).split("\n").map(line => {
  200. if (line.indexOf(":") !== -1) {
  201. line = line.split(":").pop();
  202. }
  203. line = line.trim();
  204. if (line[0] === "/") {
  205. line = line.slice(1);
  206. }
  207. return line;
  208. });
  209. const ordering = [];
  210. for (const file of orderingFiles) {
  211. const pathComponents = file.split(path.sep);
  212. for (const pathComponent of pathComponents) {
  213. ordering.push(path.join(src, pathComponent));
  214. }
  215. }
  216. const sortedFiles = [];
  217. let missing = 0;
  218. const total = filenames.length;
  219. for (const file of ordering) {
  220. if (!sortedFiles.includes(file) && filenames.includes(file)) {
  221. sortedFiles.push(file);
  222. }
  223. }
  224. for (const file of filenames) {
  225. if (!sortedFiles.includes(file)) {
  226. sortedFiles.push(file);
  227. missing += 1;
  228. }
  229. }
  230. builder_util_1.log.info({ coverage: ((total - missing) / total) * 100 }, "ordering files in ASAR archive");
  231. return sortedFiles;
  232. }
  233. function copyFileOrData(fileCopier, data, source, destination, stats) {
  234. if (data == null) {
  235. return fileCopier.copy(source, destination, stats);
  236. }
  237. else {
  238. return (0, promises_1.writeFile)(destination, data);
  239. }
  240. }
  241. //# sourceMappingURL=asarUtil.js.map