packager.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.Packager = void 0;
  4. const builder_util_1 = require("builder-util");
  5. const builder_util_runtime_1 = require("builder-util-runtime");
  6. const promise_1 = require("builder-util/out/promise");
  7. const events_1 = require("events");
  8. const fs_extra_1 = require("fs-extra");
  9. const isCI = require("is-ci");
  10. const lazy_val_1 = require("lazy-val");
  11. const path = require("path");
  12. const arch_1 = require("builder-util/out/arch");
  13. const appInfo_1 = require("./appInfo");
  14. const asar_1 = require("./asar/asar");
  15. const core_1 = require("./core");
  16. const ElectronFramework_1 = require("./electron/ElectronFramework");
  17. const LibUiFramework_1 = require("./frameworks/LibUiFramework");
  18. const platformPackager_1 = require("./platformPackager");
  19. const ProtonFramework_1 = require("./ProtonFramework");
  20. const targetFactory_1 = require("./targets/targetFactory");
  21. const config_1 = require("./util/config");
  22. const macroExpander_1 = require("./util/macroExpander");
  23. const packageDependencies_1 = require("./util/packageDependencies");
  24. const packageMetadata_1 = require("./util/packageMetadata");
  25. const repositoryInfo_1 = require("./util/repositoryInfo");
  26. const yarn_1 = require("./util/yarn");
  27. const version_1 = require("./version");
  28. const os_1 = require("os");
  29. function addHandler(emitter, event, handler) {
  30. emitter.on(event, handler);
  31. }
  32. async function createFrameworkInfo(configuration, packager) {
  33. let framework = configuration.framework;
  34. if (framework != null) {
  35. framework = framework.toLowerCase();
  36. }
  37. let nodeVersion = configuration.nodeVersion;
  38. if (framework === "electron" || framework == null) {
  39. return await (0, ElectronFramework_1.createElectronFrameworkSupport)(configuration, packager);
  40. }
  41. if (nodeVersion == null || nodeVersion === "current") {
  42. nodeVersion = process.versions.node;
  43. }
  44. const distMacOsName = `${packager.appInfo.productFilename}.app`;
  45. const isUseLaunchUi = configuration.launchUiVersion !== false;
  46. if (framework === "proton" || framework === "proton-native") {
  47. return new ProtonFramework_1.ProtonFramework(nodeVersion, distMacOsName, isUseLaunchUi);
  48. }
  49. else if (framework === "libui") {
  50. return new LibUiFramework_1.LibUiFramework(nodeVersion, distMacOsName, isUseLaunchUi);
  51. }
  52. else {
  53. throw new builder_util_1.InvalidConfigurationError(`Unknown framework: ${framework}`);
  54. }
  55. }
  56. class Packager {
  57. //noinspection JSUnusedGlobalSymbols
  58. constructor(options, cancellationToken = new builder_util_runtime_1.CancellationToken()) {
  59. this.cancellationToken = cancellationToken;
  60. this._metadata = null;
  61. this._nodeModulesHandledExternally = false;
  62. this._isPrepackedAppAsar = false;
  63. this._devMetadata = null;
  64. this._configuration = null;
  65. this.isTwoPackageJsonProjectLayoutUsed = false;
  66. this.eventEmitter = new events_1.EventEmitter();
  67. this._appInfo = null;
  68. this.tempDirManager = new builder_util_1.TmpDir("packager");
  69. this._repositoryInfo = new lazy_val_1.Lazy(() => (0, repositoryInfo_1.getRepositoryInfo)(this.projectDir, this.metadata, this.devMetadata));
  70. this.afterPackHandlers = [];
  71. this.debugLogger = new builder_util_1.DebugLogger(builder_util_1.log.isDebugEnabled);
  72. this.nodeDependencyInfo = new Map();
  73. this.stageDirPathCustomizer = (target, packager, arch) => {
  74. return path.join(target.outDir, `__${target.name}-${(0, arch_1.getArtifactArchName)(arch, target.name)}`);
  75. };
  76. this._buildResourcesDir = null;
  77. this._framework = null;
  78. this.toDispose = [];
  79. if ("devMetadata" in options) {
  80. throw new builder_util_1.InvalidConfigurationError("devMetadata in the options is deprecated, please use config instead");
  81. }
  82. if ("extraMetadata" in options) {
  83. throw new builder_util_1.InvalidConfigurationError("extraMetadata in the options is deprecated, please use config.extraMetadata instead");
  84. }
  85. const targets = options.targets || new Map();
  86. if (options.targets == null) {
  87. options.targets = targets;
  88. }
  89. function processTargets(platform, types) {
  90. function commonArch(currentIfNotSpecified) {
  91. const result = Array();
  92. return result.length === 0 && currentIfNotSpecified ? [(0, builder_util_1.archFromString)(process.arch)] : result;
  93. }
  94. let archToType = targets.get(platform);
  95. if (archToType == null) {
  96. archToType = new Map();
  97. targets.set(platform, archToType);
  98. }
  99. if (types.length === 0) {
  100. for (const arch of commonArch(false)) {
  101. archToType.set(arch, []);
  102. }
  103. return;
  104. }
  105. for (const type of types) {
  106. const suffixPos = type.lastIndexOf(":");
  107. if (suffixPos > 0) {
  108. (0, builder_util_1.addValue)(archToType, (0, builder_util_1.archFromString)(type.substring(suffixPos + 1)), type.substring(0, suffixPos));
  109. }
  110. else {
  111. for (const arch of commonArch(true)) {
  112. (0, builder_util_1.addValue)(archToType, arch, type);
  113. }
  114. }
  115. }
  116. }
  117. if (options.mac != null) {
  118. processTargets(core_1.Platform.MAC, options.mac);
  119. }
  120. if (options.linux != null) {
  121. processTargets(core_1.Platform.LINUX, options.linux);
  122. }
  123. if (options.win != null) {
  124. processTargets(core_1.Platform.WINDOWS, options.win);
  125. }
  126. this.projectDir = options.projectDir == null ? process.cwd() : path.resolve(options.projectDir);
  127. this._appDir = this.projectDir;
  128. this.options = {
  129. ...options,
  130. prepackaged: options.prepackaged == null ? null : path.resolve(this.projectDir, options.prepackaged),
  131. };
  132. try {
  133. builder_util_1.log.info({ version: version_1.PACKAGE_VERSION, os: (0, os_1.release)() }, "electron-builder");
  134. }
  135. catch (e) {
  136. // error in dev mode without babel
  137. if (!(e instanceof ReferenceError)) {
  138. throw e;
  139. }
  140. }
  141. }
  142. get appDir() {
  143. return this._appDir;
  144. }
  145. get metadata() {
  146. return this._metadata;
  147. }
  148. get areNodeModulesHandledExternally() {
  149. return this._nodeModulesHandledExternally;
  150. }
  151. get isPrepackedAppAsar() {
  152. return this._isPrepackedAppAsar;
  153. }
  154. get devMetadata() {
  155. return this._devMetadata;
  156. }
  157. get config() {
  158. return this._configuration;
  159. }
  160. get appInfo() {
  161. return this._appInfo;
  162. }
  163. get repositoryInfo() {
  164. return this._repositoryInfo.value;
  165. }
  166. getNodeDependencyInfo(platform) {
  167. let key = "";
  168. let excludedDependencies = null;
  169. if (platform != null && this.framework.getExcludedDependencies != null) {
  170. excludedDependencies = this.framework.getExcludedDependencies(platform);
  171. if (excludedDependencies != null) {
  172. key += `-${platform.name}`;
  173. }
  174. }
  175. let result = this.nodeDependencyInfo.get(key);
  176. if (result == null) {
  177. result = (0, packageDependencies_1.createLazyProductionDeps)(this.appDir, excludedDependencies);
  178. this.nodeDependencyInfo.set(key, result);
  179. }
  180. return result;
  181. }
  182. get buildResourcesDir() {
  183. let result = this._buildResourcesDir;
  184. if (result == null) {
  185. result = path.resolve(this.projectDir, this.relativeBuildResourcesDirname);
  186. this._buildResourcesDir = result;
  187. }
  188. return result;
  189. }
  190. get relativeBuildResourcesDirname() {
  191. return this.config.directories.buildResources;
  192. }
  193. get framework() {
  194. return this._framework;
  195. }
  196. disposeOnBuildFinish(disposer) {
  197. this.toDispose.push(disposer);
  198. }
  199. addAfterPackHandler(handler) {
  200. this.afterPackHandlers.push(handler);
  201. }
  202. artifactCreated(handler) {
  203. addHandler(this.eventEmitter, "artifactCreated", handler);
  204. return this;
  205. }
  206. async callArtifactBuildStarted(event, logFields) {
  207. builder_util_1.log.info(logFields || {
  208. target: event.targetPresentableName,
  209. arch: event.arch == null ? null : builder_util_1.Arch[event.arch],
  210. file: builder_util_1.log.filePath(event.file),
  211. }, "building");
  212. const handler = (0, platformPackager_1.resolveFunction)(this.config.artifactBuildStarted, "artifactBuildStarted");
  213. if (handler != null) {
  214. await Promise.resolve(handler(event));
  215. }
  216. }
  217. /**
  218. * Only for sub artifacts (update info), for main artifacts use `callArtifactBuildCompleted`.
  219. */
  220. dispatchArtifactCreated(event) {
  221. this.eventEmitter.emit("artifactCreated", event);
  222. }
  223. async callArtifactBuildCompleted(event) {
  224. const handler = (0, platformPackager_1.resolveFunction)(this.config.artifactBuildCompleted, "artifactBuildCompleted");
  225. if (handler != null) {
  226. await Promise.resolve(handler(event));
  227. }
  228. this.dispatchArtifactCreated(event);
  229. }
  230. async callAppxManifestCreated(path) {
  231. const handler = (0, platformPackager_1.resolveFunction)(this.config.appxManifestCreated, "appxManifestCreated");
  232. if (handler != null) {
  233. await Promise.resolve(handler(path));
  234. }
  235. }
  236. async callMsiProjectCreated(path) {
  237. const handler = (0, platformPackager_1.resolveFunction)(this.config.msiProjectCreated, "msiProjectCreated");
  238. if (handler != null) {
  239. await Promise.resolve(handler(path));
  240. }
  241. }
  242. async build() {
  243. let configPath = null;
  244. let configFromOptions = this.options.config;
  245. if (typeof configFromOptions === "string") {
  246. // it is a path to config file
  247. configPath = configFromOptions;
  248. configFromOptions = null;
  249. }
  250. else if (configFromOptions != null && typeof configFromOptions.extends === "string" && configFromOptions.extends.includes(".")) {
  251. configPath = configFromOptions.extends;
  252. delete configFromOptions.extends;
  253. }
  254. const projectDir = this.projectDir;
  255. const devPackageFile = path.join(projectDir, "package.json");
  256. this._devMetadata = await (0, promise_1.orNullIfFileNotExist)((0, packageMetadata_1.readPackageJson)(devPackageFile));
  257. const devMetadata = this.devMetadata;
  258. const configuration = await (0, config_1.getConfig)(projectDir, configPath, configFromOptions, new lazy_val_1.Lazy(() => Promise.resolve(devMetadata)));
  259. if (builder_util_1.log.isDebugEnabled) {
  260. builder_util_1.log.debug({ config: getSafeEffectiveConfig(configuration) }, "effective config");
  261. }
  262. this._appDir = await (0, config_1.computeDefaultAppDirectory)(projectDir, configuration.directories.app);
  263. this.isTwoPackageJsonProjectLayoutUsed = this._appDir !== projectDir;
  264. const appPackageFile = this.isTwoPackageJsonProjectLayoutUsed ? path.join(this.appDir, "package.json") : devPackageFile;
  265. // tslint:disable:prefer-conditional-expression
  266. if (this.devMetadata != null && !this.isTwoPackageJsonProjectLayoutUsed) {
  267. this._metadata = this.devMetadata;
  268. }
  269. else {
  270. this._metadata = await this.readProjectMetadataIfTwoPackageStructureOrPrepacked(appPackageFile);
  271. }
  272. (0, builder_util_1.deepAssign)(this.metadata, configuration.extraMetadata);
  273. if (this.isTwoPackageJsonProjectLayoutUsed) {
  274. builder_util_1.log.debug({ devPackageFile, appPackageFile }, "two package.json structure is used");
  275. }
  276. (0, packageMetadata_1.checkMetadata)(this.metadata, this.devMetadata, appPackageFile, devPackageFile);
  277. return await this._build(configuration, this._metadata, this._devMetadata);
  278. }
  279. // external caller of this method always uses isTwoPackageJsonProjectLayoutUsed=false and appDir=projectDir, no way (and need) to use another values
  280. async _build(configuration, metadata, devMetadata, repositoryInfo) {
  281. await (0, config_1.validateConfig)(configuration, this.debugLogger);
  282. this._configuration = configuration;
  283. this._metadata = metadata;
  284. this._devMetadata = devMetadata;
  285. if (repositoryInfo != null) {
  286. this._repositoryInfo.value = Promise.resolve(repositoryInfo);
  287. }
  288. this._appInfo = new appInfo_1.AppInfo(this, null);
  289. this._framework = await createFrameworkInfo(this.config, this);
  290. const commonOutDirWithoutPossibleOsMacro = path.resolve(this.projectDir, (0, macroExpander_1.expandMacro)(configuration.directories.output, null, this._appInfo, {
  291. os: "",
  292. }));
  293. if (!isCI && process.stdout.isTTY) {
  294. const effectiveConfigFile = path.join(commonOutDirWithoutPossibleOsMacro, "builder-effective-config.yaml");
  295. builder_util_1.log.info({ file: builder_util_1.log.filePath(effectiveConfigFile) }, "writing effective config");
  296. await (0, fs_extra_1.outputFile)(effectiveConfigFile, getSafeEffectiveConfig(configuration));
  297. }
  298. // because artifact event maybe dispatched several times for different publish providers
  299. const artifactPaths = new Set();
  300. this.artifactCreated(event => {
  301. if (event.file != null) {
  302. artifactPaths.add(event.file);
  303. }
  304. });
  305. this.disposeOnBuildFinish(() => this.tempDirManager.cleanup());
  306. const platformToTargets = await (0, promise_1.executeFinally)(this.doBuild(), async () => {
  307. if (this.debugLogger.isEnabled) {
  308. await this.debugLogger.save(path.join(commonOutDirWithoutPossibleOsMacro, "builder-debug.yml"));
  309. }
  310. const toDispose = this.toDispose.slice();
  311. this.toDispose.length = 0;
  312. for (const disposer of toDispose) {
  313. await disposer().catch((e) => {
  314. builder_util_1.log.warn({ error: e }, "cannot dispose");
  315. });
  316. }
  317. });
  318. return {
  319. outDir: commonOutDirWithoutPossibleOsMacro,
  320. artifactPaths: Array.from(artifactPaths),
  321. platformToTargets,
  322. configuration,
  323. };
  324. }
  325. async readProjectMetadataIfTwoPackageStructureOrPrepacked(appPackageFile) {
  326. let data = await (0, promise_1.orNullIfFileNotExist)((0, packageMetadata_1.readPackageJson)(appPackageFile));
  327. if (data != null) {
  328. return data;
  329. }
  330. data = await (0, promise_1.orNullIfFileNotExist)((0, asar_1.readAsarJson)(path.join(this.projectDir, "app.asar"), "package.json"));
  331. if (data != null) {
  332. this._isPrepackedAppAsar = true;
  333. return data;
  334. }
  335. throw new Error(`Cannot find package.json in the ${path.dirname(appPackageFile)}`);
  336. }
  337. async doBuild() {
  338. const taskManager = new builder_util_1.AsyncTaskManager(this.cancellationToken);
  339. const syncTargetsIfAny = [];
  340. const platformToTarget = new Map();
  341. const createdOutDirs = new Set();
  342. for (const [platform, archToType] of this.options.targets) {
  343. if (this.cancellationToken.cancelled) {
  344. break;
  345. }
  346. if (platform === core_1.Platform.MAC && process.platform === core_1.Platform.WINDOWS.nodeName) {
  347. throw new builder_util_1.InvalidConfigurationError("Build for macOS is supported only on macOS, please see https://electron.build/multi-platform-build");
  348. }
  349. const packager = await this.createHelper(platform);
  350. const nameToTarget = new Map();
  351. platformToTarget.set(platform, nameToTarget);
  352. for (const [arch, targetNames] of (0, targetFactory_1.computeArchToTargetNamesMap)(archToType, packager, platform)) {
  353. if (this.cancellationToken.cancelled) {
  354. break;
  355. }
  356. // support os and arch macro in output value
  357. const outDir = path.resolve(this.projectDir, packager.expandMacro(this._configuration.directories.output, builder_util_1.Arch[arch]));
  358. const targetList = (0, targetFactory_1.createTargets)(nameToTarget, targetNames.length === 0 ? packager.defaultTarget : targetNames, outDir, packager);
  359. await createOutDirIfNeed(targetList, createdOutDirs);
  360. await packager.pack(outDir, arch, targetList, taskManager);
  361. }
  362. if (this.cancellationToken.cancelled) {
  363. break;
  364. }
  365. for (const target of nameToTarget.values()) {
  366. if (target.isAsyncSupported) {
  367. taskManager.addTask(target.finishBuild());
  368. }
  369. else {
  370. syncTargetsIfAny.push(target);
  371. }
  372. }
  373. }
  374. await taskManager.awaitTasks();
  375. for (const target of syncTargetsIfAny) {
  376. await target.finishBuild();
  377. }
  378. return platformToTarget;
  379. }
  380. async createHelper(platform) {
  381. if (this.options.platformPackagerFactory != null) {
  382. return this.options.platformPackagerFactory(this, platform);
  383. }
  384. switch (platform) {
  385. case core_1.Platform.MAC: {
  386. const helperClass = (await Promise.resolve().then(() => require("./macPackager"))).default;
  387. return new helperClass(this);
  388. }
  389. case core_1.Platform.WINDOWS: {
  390. const helperClass = (await Promise.resolve().then(() => require("./winPackager"))).WinPackager;
  391. return new helperClass(this);
  392. }
  393. case core_1.Platform.LINUX:
  394. return new (await Promise.resolve().then(() => require("./linuxPackager"))).LinuxPackager(this);
  395. default:
  396. throw new Error(`Unknown platform: ${platform}`);
  397. }
  398. }
  399. async installAppDependencies(platform, arch) {
  400. if (this.options.prepackaged != null || !this.framework.isNpmRebuildRequired) {
  401. return;
  402. }
  403. const frameworkInfo = { version: this.framework.version, useCustomDist: true };
  404. const config = this.config;
  405. if (config.nodeGypRebuild === true) {
  406. await (0, yarn_1.nodeGypRebuild)(platform.nodeName, builder_util_1.Arch[arch], frameworkInfo);
  407. }
  408. if (config.npmRebuild === false) {
  409. builder_util_1.log.info({ reason: "npmRebuild is set to false" }, "skipped dependencies rebuild");
  410. return;
  411. }
  412. const beforeBuild = (0, platformPackager_1.resolveFunction)(config.beforeBuild, "beforeBuild");
  413. if (beforeBuild != null) {
  414. const performDependenciesInstallOrRebuild = await beforeBuild({
  415. appDir: this.appDir,
  416. electronVersion: this.config.electronVersion,
  417. platform,
  418. arch: builder_util_1.Arch[arch],
  419. });
  420. // If beforeBuild resolves to false, it means that handling node_modules is done outside of electron-builder.
  421. this._nodeModulesHandledExternally = !performDependenciesInstallOrRebuild;
  422. if (!performDependenciesInstallOrRebuild) {
  423. return;
  424. }
  425. }
  426. if (config.buildDependenciesFromSource === true && platform.nodeName !== process.platform) {
  427. builder_util_1.log.info({ reason: "platform is different and buildDependenciesFromSource is set to true" }, "skipped dependencies rebuild");
  428. }
  429. else {
  430. await (0, yarn_1.installOrRebuild)(config, this.appDir, {
  431. frameworkInfo,
  432. platform: platform.nodeName,
  433. arch: builder_util_1.Arch[arch],
  434. productionDeps: this.getNodeDependencyInfo(null),
  435. });
  436. }
  437. }
  438. async afterPack(context) {
  439. const afterPack = (0, platformPackager_1.resolveFunction)(this.config.afterPack, "afterPack");
  440. const handlers = this.afterPackHandlers.slice();
  441. if (afterPack != null) {
  442. // user handler should be last
  443. handlers.push(afterPack);
  444. }
  445. for (const handler of handlers) {
  446. await Promise.resolve(handler(context));
  447. }
  448. }
  449. }
  450. exports.Packager = Packager;
  451. function createOutDirIfNeed(targetList, createdOutDirs) {
  452. const ourDirs = new Set();
  453. for (const target of targetList) {
  454. // noinspection SuspiciousInstanceOfGuard
  455. if (target instanceof targetFactory_1.NoOpTarget) {
  456. continue;
  457. }
  458. const outDir = target.outDir;
  459. if (!createdOutDirs.has(outDir)) {
  460. ourDirs.add(outDir);
  461. }
  462. }
  463. if (ourDirs.size === 0) {
  464. return Promise.resolve();
  465. }
  466. return Promise.all(Array.from(ourDirs)
  467. .sort()
  468. .map(dir => {
  469. return (0, fs_extra_1.mkdirs)(dir)
  470. .then(() => (0, fs_extra_1.chmod)(dir, 0o755) /* set explicitly */)
  471. .then(() => createdOutDirs.add(dir));
  472. }));
  473. }
  474. function getSafeEffectiveConfig(configuration) {
  475. const o = JSON.parse((0, builder_util_1.safeStringifyJson)(configuration));
  476. if (o.cscLink != null) {
  477. o.cscLink = "<hidden by builder>";
  478. }
  479. return (0, builder_util_1.serializeToYaml)(o, true);
  480. }
  481. //# sourceMappingURL=packager.js.map