winPackager.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.WinPackager = void 0;
  4. const bluebird_lst_1 = require("bluebird-lst");
  5. const builder_util_1 = require("builder-util");
  6. const builder_util_runtime_1 = require("builder-util-runtime");
  7. const fs_1 = require("builder-util/out/fs");
  8. const crypto_1 = require("crypto");
  9. const promises_1 = require("fs/promises");
  10. const isCI = require("is-ci");
  11. const lazy_val_1 = require("lazy-val");
  12. const path = require("path");
  13. const codesign_1 = require("./codeSign/codesign");
  14. const windowsCodeSign_1 = require("./codeSign/windowsCodeSign");
  15. const core_1 = require("./core");
  16. const platformPackager_1 = require("./platformPackager");
  17. const NsisTarget_1 = require("./targets/nsis/NsisTarget");
  18. const nsisUtil_1 = require("./targets/nsis/nsisUtil");
  19. const WebInstallerTarget_1 = require("./targets/nsis/WebInstallerTarget");
  20. const targetFactory_1 = require("./targets/targetFactory");
  21. const cacheManager_1 = require("./util/cacheManager");
  22. const flags_1 = require("./util/flags");
  23. const timer_1 = require("./util/timer");
  24. const vm_1 = require("./vm/vm");
  25. const wine_1 = require("./wine");
  26. class WinPackager extends platformPackager_1.PlatformPackager {
  27. constructor(info) {
  28. super(info, core_1.Platform.WINDOWS);
  29. this.cscInfo = new lazy_val_1.Lazy(() => {
  30. const platformSpecificBuildOptions = this.platformSpecificBuildOptions;
  31. if (platformSpecificBuildOptions.certificateSubjectName != null || platformSpecificBuildOptions.certificateSha1 != null) {
  32. return this.vm.value
  33. .then(vm => (0, windowsCodeSign_1.getCertificateFromStoreInfo)(platformSpecificBuildOptions, vm))
  34. .catch((e) => {
  35. // https://github.com/electron-userland/electron-builder/pull/2397
  36. if (platformSpecificBuildOptions.sign == null) {
  37. throw e;
  38. }
  39. else {
  40. builder_util_1.log.debug({ error: e }, "getCertificateFromStoreInfo error");
  41. return null;
  42. }
  43. });
  44. }
  45. const certificateFile = platformSpecificBuildOptions.certificateFile;
  46. if (certificateFile != null) {
  47. const certificatePassword = this.getCscPassword();
  48. return Promise.resolve({
  49. file: certificateFile,
  50. password: certificatePassword == null ? null : certificatePassword.trim(),
  51. });
  52. }
  53. const cscLink = this.getCscLink("WIN_CSC_LINK");
  54. if (cscLink == null) {
  55. return Promise.resolve(null);
  56. }
  57. return ((0, codesign_1.importCertificate)(cscLink, this.info.tempDirManager, this.projectDir)
  58. // before then
  59. .catch((e) => {
  60. if (e instanceof builder_util_1.InvalidConfigurationError) {
  61. throw new builder_util_1.InvalidConfigurationError(`Env WIN_CSC_LINK is not correct, cannot resolve: ${e.message}`);
  62. }
  63. else {
  64. throw e;
  65. }
  66. })
  67. .then(path => {
  68. return {
  69. file: path,
  70. password: this.getCscPassword(),
  71. };
  72. }));
  73. });
  74. this._iconPath = new lazy_val_1.Lazy(() => this.getOrConvertIcon("ico"));
  75. this.vm = new lazy_val_1.Lazy(() => (process.platform === "win32" ? Promise.resolve(new vm_1.VmManager()) : (0, vm_1.getWindowsVm)(this.debugLogger)));
  76. this.computedPublisherName = new lazy_val_1.Lazy(async () => {
  77. const publisherName = this.platformSpecificBuildOptions.publisherName;
  78. if (publisherName === null) {
  79. return null;
  80. }
  81. else if (publisherName != null) {
  82. return (0, builder_util_1.asArray)(publisherName);
  83. }
  84. const certInfo = await this.lazyCertInfo.value;
  85. return certInfo == null ? null : [certInfo.commonName];
  86. });
  87. this.lazyCertInfo = new lazy_val_1.Lazy(async () => {
  88. const cscInfo = await this.cscInfo.value;
  89. if (cscInfo == null) {
  90. return null;
  91. }
  92. if ("subject" in cscInfo) {
  93. const bloodyMicrosoftSubjectDn = cscInfo.subject;
  94. return {
  95. commonName: (0, builder_util_runtime_1.parseDn)(bloodyMicrosoftSubjectDn).get("CN"),
  96. bloodyMicrosoftSubjectDn,
  97. };
  98. }
  99. const cscFile = cscInfo.file;
  100. if (cscFile == null) {
  101. return null;
  102. }
  103. return await (0, windowsCodeSign_1.getCertInfo)(cscFile, cscInfo.password || "");
  104. });
  105. }
  106. get isForceCodeSigningVerification() {
  107. return this.platformSpecificBuildOptions.verifyUpdateCodeSignature !== false;
  108. }
  109. get defaultTarget() {
  110. return ["nsis"];
  111. }
  112. doGetCscPassword() {
  113. return (0, platformPackager_1.chooseNotNull)((0, platformPackager_1.chooseNotNull)(this.platformSpecificBuildOptions.certificatePassword, process.env.WIN_CSC_KEY_PASSWORD), super.doGetCscPassword());
  114. }
  115. createTargets(targets, mapper) {
  116. let copyElevateHelper;
  117. const getCopyElevateHelper = () => {
  118. if (copyElevateHelper == null) {
  119. copyElevateHelper = new nsisUtil_1.CopyElevateHelper();
  120. }
  121. return copyElevateHelper;
  122. };
  123. let helper;
  124. const getHelper = () => {
  125. if (helper == null) {
  126. helper = new nsisUtil_1.AppPackageHelper(getCopyElevateHelper());
  127. }
  128. return helper;
  129. };
  130. for (const name of targets) {
  131. if (name === core_1.DIR_TARGET) {
  132. continue;
  133. }
  134. if (name === "nsis" || name === "portable") {
  135. mapper(name, outDir => new NsisTarget_1.NsisTarget(this, outDir, name, getHelper()));
  136. }
  137. else if (name === "nsis-web") {
  138. // package file format differs from nsis target
  139. mapper(name, outDir => new WebInstallerTarget_1.WebInstallerTarget(this, path.join(outDir, name), name, new nsisUtil_1.AppPackageHelper(getCopyElevateHelper())));
  140. }
  141. else {
  142. const targetClass = (() => {
  143. switch (name) {
  144. case "squirrel":
  145. try {
  146. return require("electron-builder-squirrel-windows").default;
  147. }
  148. catch (e) {
  149. throw new builder_util_1.InvalidConfigurationError(`Module electron-builder-squirrel-windows must be installed in addition to build Squirrel.Windows: ${e.stack || e}`);
  150. }
  151. case "appx":
  152. return require("./targets/AppxTarget").default;
  153. case "msi":
  154. return require("./targets/MsiTarget").default;
  155. case "msiwrapped":
  156. return require("./targets/MsiWrappedTarget").default;
  157. default:
  158. return null;
  159. }
  160. })();
  161. mapper(name, outDir => (targetClass === null ? (0, targetFactory_1.createCommonTarget)(name, outDir, this) : new targetClass(this, outDir, name)));
  162. }
  163. }
  164. }
  165. getIconPath() {
  166. return this._iconPath.value;
  167. }
  168. async sign(file, logMessagePrefix) {
  169. const signOptions = {
  170. path: file,
  171. name: this.appInfo.productName,
  172. site: await this.appInfo.computePackageUrl(),
  173. options: this.platformSpecificBuildOptions,
  174. };
  175. const cscInfo = await this.cscInfo.value;
  176. if (cscInfo == null) {
  177. if (this.platformSpecificBuildOptions.sign != null) {
  178. return (0, windowsCodeSign_1.sign)(signOptions, this);
  179. }
  180. else if (this.forceCodeSigning) {
  181. throw new builder_util_1.InvalidConfigurationError(`App is not signed and "forceCodeSigning" is set to true, please ensure that code signing configuration is correct, please see https://electron.build/code-signing`);
  182. }
  183. return false;
  184. }
  185. if (logMessagePrefix == null) {
  186. logMessagePrefix = "signing";
  187. }
  188. if ("file" in cscInfo) {
  189. builder_util_1.log.info({
  190. file: builder_util_1.log.filePath(file),
  191. certificateFile: cscInfo.file,
  192. }, logMessagePrefix);
  193. }
  194. else {
  195. const info = cscInfo;
  196. builder_util_1.log.info({
  197. file: builder_util_1.log.filePath(file),
  198. subject: info.subject,
  199. thumbprint: info.thumbprint,
  200. store: info.store,
  201. user: info.isLocalMachineStore ? "local machine" : "current user",
  202. }, logMessagePrefix);
  203. }
  204. return this.doSign({
  205. ...signOptions,
  206. cscInfo,
  207. options: {
  208. ...this.platformSpecificBuildOptions,
  209. },
  210. });
  211. }
  212. async doSign(options) {
  213. for (let i = 0; i < 3; i++) {
  214. try {
  215. await (0, windowsCodeSign_1.sign)(options, this);
  216. return true;
  217. }
  218. catch (e) {
  219. // https://github.com/electron-userland/electron-builder/issues/1414
  220. const message = e.message;
  221. if (message != null && message.includes("Couldn't resolve host name")) {
  222. builder_util_1.log.warn({ error: message, attempt: i + 1 }, `cannot sign`);
  223. continue;
  224. }
  225. throw e;
  226. }
  227. }
  228. return false;
  229. }
  230. async signAndEditResources(file, arch, outDir, internalName, requestedExecutionLevel) {
  231. const appInfo = this.appInfo;
  232. const files = [];
  233. const args = [
  234. file,
  235. "--set-version-string",
  236. "FileDescription",
  237. appInfo.productName,
  238. "--set-version-string",
  239. "ProductName",
  240. appInfo.productName,
  241. "--set-version-string",
  242. "LegalCopyright",
  243. appInfo.copyright,
  244. "--set-file-version",
  245. appInfo.shortVersion || appInfo.buildVersion,
  246. "--set-product-version",
  247. appInfo.shortVersionWindows || appInfo.getVersionInWeirdWindowsForm(),
  248. ];
  249. if (internalName != null) {
  250. args.push("--set-version-string", "InternalName", internalName, "--set-version-string", "OriginalFilename", "");
  251. }
  252. if (requestedExecutionLevel != null && requestedExecutionLevel !== "asInvoker") {
  253. args.push("--set-requested-execution-level", requestedExecutionLevel);
  254. }
  255. (0, builder_util_1.use)(appInfo.companyName, it => args.push("--set-version-string", "CompanyName", it));
  256. (0, builder_util_1.use)(this.platformSpecificBuildOptions.legalTrademarks, it => args.push("--set-version-string", "LegalTrademarks", it));
  257. const iconPath = await this.getIconPath();
  258. (0, builder_util_1.use)(iconPath, it => {
  259. files.push(it);
  260. args.push("--set-icon", it);
  261. });
  262. const config = this.config;
  263. const cscInfoForCacheDigest = !(0, flags_1.isBuildCacheEnabled)() || isCI || config.electronDist != null ? null : await this.cscInfo.value;
  264. let buildCacheManager = null;
  265. // resources editing doesn't change executable for the same input and executed quickly - no need to complicate
  266. if (cscInfoForCacheDigest != null) {
  267. const cscFile = cscInfoForCacheDigest.file;
  268. if (cscFile != null) {
  269. files.push(cscFile);
  270. }
  271. const timer = (0, timer_1.time)("executable cache");
  272. const hash = (0, crypto_1.createHash)("sha512");
  273. hash.update(config.electronVersion || "no electronVersion");
  274. hash.update(JSON.stringify(this.platformSpecificBuildOptions));
  275. hash.update(JSON.stringify(args));
  276. hash.update(this.platformSpecificBuildOptions.certificateSha1 || "no certificateSha1");
  277. hash.update(this.platformSpecificBuildOptions.certificateSubjectName || "no subjectName");
  278. buildCacheManager = new cacheManager_1.BuildCacheManager(outDir, file, arch);
  279. if (await buildCacheManager.copyIfValid(await (0, cacheManager_1.digest)(hash, files))) {
  280. timer.end();
  281. return;
  282. }
  283. timer.end();
  284. }
  285. const timer = (0, timer_1.time)("wine&sign");
  286. // rcedit crashed of executed using wine, resourcehacker works
  287. if (process.platform === "win32" || process.platform === "darwin") {
  288. await (0, builder_util_1.executeAppBuilder)(["rcedit", "--args", JSON.stringify(args)], undefined /* child-process */, {}, 3 /* retry three times */);
  289. }
  290. else if (this.info.framework.name === "electron") {
  291. const vendorPath = await (0, windowsCodeSign_1.getSignVendorPath)();
  292. await (0, wine_1.execWine)(path.join(vendorPath, "rcedit-ia32.exe"), path.join(vendorPath, "rcedit-x64.exe"), args);
  293. }
  294. await this.sign(file);
  295. timer.end();
  296. if (buildCacheManager != null) {
  297. await buildCacheManager.save();
  298. }
  299. }
  300. shouldSignFile(file) {
  301. var _a;
  302. const shouldSignDll = this.platformSpecificBuildOptions.signDlls === true && file.endsWith(".dll");
  303. const shouldSignExplicit = !!((_a = this.platformSpecificBuildOptions.signExts) === null || _a === void 0 ? void 0 : _a.some(ext => file.endsWith(ext)));
  304. return shouldSignDll || shouldSignExplicit || file.endsWith(".exe");
  305. }
  306. createTransformerForExtraFiles(packContext) {
  307. if (this.platformSpecificBuildOptions.signAndEditExecutable === false) {
  308. return null;
  309. }
  310. return file => {
  311. if (this.shouldSignFile(file)) {
  312. const parentDir = path.dirname(file);
  313. if (parentDir !== packContext.appOutDir) {
  314. return new fs_1.CopyFileTransformer(file => this.sign(file));
  315. }
  316. }
  317. return null;
  318. };
  319. }
  320. async signApp(packContext, isAsar) {
  321. const exeFileName = `${this.appInfo.productFilename}.exe`;
  322. if (this.platformSpecificBuildOptions.signAndEditExecutable === false) {
  323. return false;
  324. }
  325. await bluebird_lst_1.default.map((0, promises_1.readdir)(packContext.appOutDir), (file) => {
  326. if (file === exeFileName) {
  327. return this.signAndEditResources(path.join(packContext.appOutDir, exeFileName), packContext.arch, packContext.outDir, path.basename(exeFileName, ".exe"), this.platformSpecificBuildOptions.requestedExecutionLevel);
  328. }
  329. else if (this.shouldSignFile(file)) {
  330. return this.sign(path.join(packContext.appOutDir, file));
  331. }
  332. return null;
  333. });
  334. if (!isAsar) {
  335. return true;
  336. }
  337. const filesPromise = (filepath) => {
  338. const outDir = path.join(packContext.appOutDir, ...filepath);
  339. return (0, fs_1.walk)(outDir, (file, stat) => stat.isDirectory() || this.shouldSignFile(file));
  340. };
  341. const filesToSign = await Promise.all([filesPromise(["resources", "app.asar.unpacked"]), filesPromise(["swiftshader"])]);
  342. await bluebird_lst_1.default.map(filesToSign.flat(1), file => this.sign(file), { concurrency: 4 });
  343. return true;
  344. }
  345. }
  346. exports.WinPackager = WinPackager;
  347. //# sourceMappingURL=winPackager.js.map