UNPKG

21.3 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.Packager = void 0;
4const builder_util_1 = require("builder-util");
5const builder_util_runtime_1 = require("builder-util-runtime");
6const promise_1 = require("builder-util/out/promise");
7const events_1 = require("events");
8const fs_extra_1 = require("fs-extra");
9const isCI = require("is-ci");
10const lazy_val_1 = require("lazy-val");
11const path = require("path");
12const arch_1 = require("builder-util/out/arch");
13const appInfo_1 = require("./appInfo");
14const asar_1 = require("./asar/asar");
15const core_1 = require("./core");
16const ElectronFramework_1 = require("./electron/ElectronFramework");
17const LibUiFramework_1 = require("./frameworks/LibUiFramework");
18const platformPackager_1 = require("./platformPackager");
19const ProtonFramework_1 = require("./ProtonFramework");
20const targetFactory_1 = require("./targets/targetFactory");
21const config_1 = require("./util/config");
22const macroExpander_1 = require("./util/macroExpander");
23const packageDependencies_1 = require("./util/packageDependencies");
24const packageMetadata_1 = require("./util/packageMetadata");
25const repositoryInfo_1 = require("./util/repositoryInfo");
26const yarn_1 = require("./util/yarn");
27const version_1 = require("./version");
28const os_1 = require("os");
29function addHandler(emitter, event, handler) {
30 emitter.on(event, handler);
31}
32async 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 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}
56class 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(() => 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}-${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 ? [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 builder_util_1.addValue(archToType, builder_util_1.archFromString(type.substring(suffixPos + 1)), type.substring(0, suffixPos));
109 }
110 else {
111 for (const arch of commonArch(true)) {
112 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: 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 = 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 = 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 this.dispatchArtifactCreated(event);
225 const handler = platformPackager_1.resolveFunction(this.config.artifactBuildCompleted, "artifactBuildCompleted");
226 if (handler != null) {
227 await Promise.resolve(handler(event));
228 }
229 }
230 async callAppxManifestCreated(path) {
231 const handler = 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 = 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 promise_1.orNullIfFileNotExist(packageMetadata_1.readPackageJson(devPackageFile));
257 const devMetadata = this.devMetadata;
258 const configuration = await 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 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 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 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 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, 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 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 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 promise_1.orNullIfFileNotExist(packageMetadata_1.readPackageJson(appPackageFile));
327 if (data != null) {
328 return data;
329 }
330 data = await promise_1.orNullIfFileNotExist(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 platformToTarget = new Map();
340 const createdOutDirs = new Set();
341 for (const [platform, archToType] of this.options.targets) {
342 if (this.cancellationToken.cancelled) {
343 break;
344 }
345 if (platform === core_1.Platform.MAC && process.platform === core_1.Platform.WINDOWS.nodeName) {
346 throw new builder_util_1.InvalidConfigurationError("Build for macOS is supported only on macOS, please see https://electron.build/multi-platform-build");
347 }
348 const packager = await this.createHelper(platform);
349 const nameToTarget = new Map();
350 platformToTarget.set(platform, nameToTarget);
351 for (const [arch, targetNames] of targetFactory_1.computeArchToTargetNamesMap(archToType, packager, platform)) {
352 if (this.cancellationToken.cancelled) {
353 break;
354 }
355 // support os and arch macro in output value
356 const outDir = path.resolve(this.projectDir, packager.expandMacro(this._configuration.directories.output, builder_util_1.Arch[arch]));
357 const targetList = targetFactory_1.createTargets(nameToTarget, targetNames.length === 0 ? packager.defaultTarget : targetNames, outDir, packager);
358 await createOutDirIfNeed(targetList, createdOutDirs);
359 await packager.pack(outDir, arch, targetList, taskManager);
360 }
361 if (this.cancellationToken.cancelled) {
362 break;
363 }
364 for (const target of nameToTarget.values()) {
365 taskManager.addTask(target.finishBuild());
366 }
367 }
368 await taskManager.awaitTasks();
369 return platformToTarget;
370 }
371 async createHelper(platform) {
372 if (this.options.platformPackagerFactory != null) {
373 return this.options.platformPackagerFactory(this, platform);
374 }
375 switch (platform) {
376 case core_1.Platform.MAC: {
377 const helperClass = (await Promise.resolve().then(() => require("./macPackager"))).default;
378 return new helperClass(this);
379 }
380 case core_1.Platform.WINDOWS: {
381 const helperClass = (await Promise.resolve().then(() => require("./winPackager"))).WinPackager;
382 return new helperClass(this);
383 }
384 case core_1.Platform.LINUX:
385 return new (await Promise.resolve().then(() => require("./linuxPackager"))).LinuxPackager(this);
386 default:
387 throw new Error(`Unknown platform: ${platform}`);
388 }
389 }
390 async installAppDependencies(platform, arch) {
391 if (this.options.prepackaged != null || !this.framework.isNpmRebuildRequired) {
392 return;
393 }
394 const frameworkInfo = { version: this.framework.version, useCustomDist: true };
395 const config = this.config;
396 if (config.nodeGypRebuild === true) {
397 await yarn_1.nodeGypRebuild(platform.nodeName, builder_util_1.Arch[arch], frameworkInfo);
398 }
399 if (config.npmRebuild === false) {
400 builder_util_1.log.info({ reason: "npmRebuild is set to false" }, "skipped dependencies rebuild");
401 return;
402 }
403 const beforeBuild = platformPackager_1.resolveFunction(config.beforeBuild, "beforeBuild");
404 if (beforeBuild != null) {
405 const performDependenciesInstallOrRebuild = await beforeBuild({
406 appDir: this.appDir,
407 electronVersion: this.config.electronVersion,
408 platform,
409 arch: builder_util_1.Arch[arch],
410 });
411 // If beforeBuild resolves to false, it means that handling node_modules is done outside of electron-builder.
412 this._nodeModulesHandledExternally = !performDependenciesInstallOrRebuild;
413 if (!performDependenciesInstallOrRebuild) {
414 return;
415 }
416 }
417 if (config.buildDependenciesFromSource === true && platform.nodeName !== process.platform) {
418 builder_util_1.log.info({ reason: "platform is different and buildDependenciesFromSource is set to true" }, "skipped dependencies rebuild");
419 }
420 else {
421 await yarn_1.installOrRebuild(config, this.appDir, {
422 frameworkInfo,
423 platform: platform.nodeName,
424 arch: builder_util_1.Arch[arch],
425 productionDeps: this.getNodeDependencyInfo(null),
426 });
427 }
428 }
429 async afterPack(context) {
430 const afterPack = platformPackager_1.resolveFunction(this.config.afterPack, "afterPack");
431 const handlers = this.afterPackHandlers.slice();
432 if (afterPack != null) {
433 // user handler should be last
434 handlers.push(afterPack);
435 }
436 for (const handler of handlers) {
437 await Promise.resolve(handler(context));
438 }
439 }
440}
441exports.Packager = Packager;
442function createOutDirIfNeed(targetList, createdOutDirs) {
443 const ourDirs = new Set();
444 for (const target of targetList) {
445 // noinspection SuspiciousInstanceOfGuard
446 if (target instanceof targetFactory_1.NoOpTarget) {
447 continue;
448 }
449 const outDir = target.outDir;
450 if (!createdOutDirs.has(outDir)) {
451 ourDirs.add(outDir);
452 }
453 }
454 if (ourDirs.size === 0) {
455 return Promise.resolve();
456 }
457 return Promise.all(Array.from(ourDirs)
458 .sort()
459 .map(dir => {
460 return fs_extra_1.mkdirs(dir)
461 .then(() => fs_extra_1.chmod(dir, 0o755) /* set explicitly */)
462 .then(() => createdOutDirs.add(dir));
463 }));
464}
465function getSafeEffectiveConfig(configuration) {
466 const o = JSON.parse(builder_util_1.safeStringifyJson(configuration));
467 if (o.cscLink != null) {
468 o.cscLink = "<hidden by builder>";
469 }
470 return builder_util_1.serializeToYaml(o, true);
471}
472//# sourceMappingURL=packager.js.map
\No newline at end of file