UNPKG

22.5 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getPublishConfigs = exports.computeDownloadUrl = exports.createPublisher = exports.getPublishConfigsForUpdateInfo = exports.getAppUpdatePublishConfiguration = exports.PublishManager = void 0;
4const bluebird_lst_1 = require("bluebird-lst");
5const builder_util_1 = require("builder-util");
6const builder_util_runtime_1 = require("builder-util-runtime");
7const debug_1 = require("debug");
8const electron_publish_1 = require("electron-publish");
9const gitHubPublisher_1 = require("electron-publish/out/gitHubPublisher");
10const multiProgress_1 = require("electron-publish/out/multiProgress");
11const s3Publisher_1 = require("./s3/s3Publisher");
12const spacesPublisher_1 = require("./s3/spacesPublisher");
13const promises_1 = require("fs/promises");
14const isCi = require("is-ci");
15const path = require("path");
16const url = require("url");
17const index_1 = require("../index");
18const macroExpander_1 = require("../util/macroExpander");
19const SnapStorePublisher_1 = require("./SnapStorePublisher");
20const updateInfoBuilder_1 = require("./updateInfoBuilder");
21const KeygenPublisher_1 = require("./KeygenPublisher");
22const BitbucketPublisher_1 = require("./BitbucketPublisher");
23const publishForPrWarning = "There are serious security concerns with PUBLISH_FOR_PULL_REQUEST=true (see the CircleCI documentation (https://circleci.com/docs/1.0/fork-pr-builds/) for details)" +
24 "\nIf you have SSH keys, sensitive env vars or AWS credentials stored in your project settings and untrusted forks can make pull requests against your repo, then this option isn't for you.";
25const debug = (0, debug_1.default)("electron-builder:publish");
26function checkOptions(publishPolicy) {
27 if (publishPolicy != null && publishPolicy !== "onTag" && publishPolicy !== "onTagOrDraft" && publishPolicy !== "always" && publishPolicy !== "never") {
28 if (typeof publishPolicy === "string") {
29 throw new builder_util_1.InvalidConfigurationError(`Expected one of "onTag", "onTagOrDraft", "always", "never", but got ${JSON.stringify(publishPolicy)}.\nPlease note that publish configuration should be specified under "config"`);
30 }
31 }
32}
33class PublishManager {
34 constructor(packager, publishOptions, cancellationToken = packager.cancellationToken) {
35 this.packager = packager;
36 this.publishOptions = publishOptions;
37 this.cancellationToken = cancellationToken;
38 this.nameToPublisher = new Map();
39 this.isPublish = false;
40 this.progress = process.stdout.isTTY ? new multiProgress_1.MultiProgress() : null;
41 this.updateFileWriteTask = [];
42 checkOptions(publishOptions.publish);
43 this.taskManager = new builder_util_1.AsyncTaskManager(cancellationToken);
44 const forcePublishForPr = process.env.PUBLISH_FOR_PULL_REQUEST === "true";
45 if (!(0, builder_util_1.isPullRequest)() || forcePublishForPr) {
46 if (publishOptions.publish === undefined) {
47 if (process.env.npm_lifecycle_event === "release") {
48 publishOptions.publish = "always";
49 }
50 else {
51 const tag = (0, electron_publish_1.getCiTag)();
52 if (tag != null) {
53 builder_util_1.log.info({ reason: "tag is defined", tag }, "artifacts will be published");
54 publishOptions.publish = "onTag";
55 }
56 else if (isCi) {
57 builder_util_1.log.info({ reason: "CI detected" }, "artifacts will be published if draft release exists");
58 publishOptions.publish = "onTagOrDraft";
59 }
60 }
61 }
62 const publishPolicy = publishOptions.publish;
63 this.isPublish = publishPolicy != null && publishOptions.publish !== "never" && (publishPolicy !== "onTag" || (0, electron_publish_1.getCiTag)() != null);
64 if (this.isPublish && forcePublishForPr) {
65 builder_util_1.log.warn(publishForPrWarning);
66 }
67 }
68 else if (publishOptions.publish !== "never") {
69 builder_util_1.log.info({
70 reason: "current build is a part of pull request",
71 solution: `set env PUBLISH_FOR_PULL_REQUEST to true to force code signing\n${publishForPrWarning}`,
72 }, "publishing will be skipped");
73 }
74 packager.addAfterPackHandler(async (event) => {
75 const packager = event.packager;
76 if (event.electronPlatformName === "darwin") {
77 if (!event.targets.some(it => it.name === "dmg" || it.name === "zip")) {
78 return;
79 }
80 }
81 else if (packager.platform === index_1.Platform.WINDOWS) {
82 if (!event.targets.some(it => isSuitableWindowsTarget(it))) {
83 return;
84 }
85 }
86 const publishConfig = await getAppUpdatePublishConfiguration(packager, event.arch, this.isPublish);
87 if (publishConfig != null) {
88 await (0, promises_1.writeFile)(path.join(packager.getResourcesDir(event.appOutDir), "app-update.yml"), (0, builder_util_1.serializeToYaml)(publishConfig));
89 }
90 });
91 packager.artifactCreated(event => {
92 const publishConfiguration = event.publishConfig;
93 if (publishConfiguration == null) {
94 this.taskManager.addTask(this.artifactCreatedWithoutExplicitPublishConfig(event));
95 }
96 else if (this.isPublish) {
97 if (debug.enabled) {
98 debug(`artifactCreated (isPublish: ${this.isPublish}): ${(0, builder_util_1.safeStringifyJson)(event, new Set(["packager"]))},\n publishConfig: ${(0, builder_util_1.safeStringifyJson)(publishConfiguration)}`);
99 }
100 this.scheduleUpload(publishConfiguration, event, this.getAppInfo(event.packager));
101 }
102 });
103 }
104 getAppInfo(platformPackager) {
105 return platformPackager == null ? this.packager.appInfo : platformPackager.appInfo;
106 }
107 async getGlobalPublishConfigurations() {
108 const publishers = this.packager.config.publish;
109 return await resolvePublishConfigurations(publishers, null, this.packager, null, true);
110 }
111 scheduleUpload(publishConfig, event, appInfo) {
112 if (publishConfig.provider === "generic") {
113 return;
114 }
115 const publisher = this.getOrCreatePublisher(publishConfig, appInfo);
116 if (publisher == null) {
117 builder_util_1.log.debug({
118 file: event.file,
119 reason: "publisher is null",
120 publishConfig: (0, builder_util_1.safeStringifyJson)(publishConfig),
121 }, "not published");
122 return;
123 }
124 const providerName = publisher.providerName;
125 if (this.publishOptions.publish === "onTagOrDraft" && (0, electron_publish_1.getCiTag)() == null && providerName !== "bitbucket" && providerName !== "github") {
126 builder_util_1.log.info({ file: event.file, reason: "current build is not for a git tag", publishPolicy: "onTagOrDraft" }, `not published to ${providerName}`);
127 return;
128 }
129 if (publishConfig.timeout) {
130 event.timeout = publishConfig.timeout;
131 }
132 this.taskManager.addTask(publisher.upload(event));
133 }
134 async artifactCreatedWithoutExplicitPublishConfig(event) {
135 const platformPackager = event.packager;
136 const target = event.target;
137 const publishConfigs = await getPublishConfigs(platformPackager, target == null ? null : target.options, event.arch, this.isPublish);
138 if (debug.enabled) {
139 debug(`artifactCreated (isPublish: ${this.isPublish}): ${(0, builder_util_1.safeStringifyJson)(event, new Set(["packager"]))},\n publishConfigs: ${(0, builder_util_1.safeStringifyJson)(publishConfigs)}`);
140 }
141 const eventFile = event.file;
142 if (publishConfigs == null) {
143 if (this.isPublish) {
144 builder_util_1.log.debug({ file: eventFile, reason: "no publish configs" }, "not published");
145 }
146 return;
147 }
148 if (this.isPublish) {
149 for (const publishConfig of publishConfigs) {
150 if (this.cancellationToken.cancelled) {
151 builder_util_1.log.debug({ file: event.file, reason: "cancelled" }, "not published");
152 break;
153 }
154 this.scheduleUpload(publishConfig, event, this.getAppInfo(platformPackager));
155 }
156 }
157 if (event.isWriteUpdateInfo &&
158 target != null &&
159 eventFile != null &&
160 !this.cancellationToken.cancelled &&
161 (platformPackager.platform !== index_1.Platform.WINDOWS || isSuitableWindowsTarget(target))) {
162 this.taskManager.addTask((0, updateInfoBuilder_1.createUpdateInfoTasks)(event, publishConfigs).then(it => this.updateFileWriteTask.push(...it)));
163 }
164 }
165 getOrCreatePublisher(publishConfig, appInfo) {
166 // to not include token into cache key
167 const providerCacheKey = (0, builder_util_1.safeStringifyJson)(publishConfig);
168 let publisher = this.nameToPublisher.get(providerCacheKey);
169 if (publisher == null) {
170 publisher = createPublisher(this, appInfo.version, publishConfig, this.publishOptions, this.packager);
171 this.nameToPublisher.set(providerCacheKey, publisher);
172 builder_util_1.log.info({ publisher: publisher.toString() }, "publishing");
173 }
174 return publisher;
175 }
176 // noinspection JSUnusedGlobalSymbols
177 cancelTasks() {
178 this.taskManager.cancelTasks();
179 this.nameToPublisher.clear();
180 }
181 async awaitTasks() {
182 await this.taskManager.awaitTasks();
183 const updateInfoFileTasks = this.updateFileWriteTask;
184 if (this.cancellationToken.cancelled || updateInfoFileTasks.length === 0) {
185 return;
186 }
187 await (0, updateInfoBuilder_1.writeUpdateInfoFiles)(updateInfoFileTasks, this.packager);
188 await this.taskManager.awaitTasks();
189 }
190}
191exports.PublishManager = PublishManager;
192async function getAppUpdatePublishConfiguration(packager, arch, errorIfCannot) {
193 const publishConfigs = await getPublishConfigsForUpdateInfo(packager, await getPublishConfigs(packager, null, arch, errorIfCannot), arch);
194 if (publishConfigs == null || publishConfigs.length === 0) {
195 return null;
196 }
197 const publishConfig = {
198 ...publishConfigs[0],
199 updaterCacheDirName: packager.appInfo.updaterCacheDirName,
200 };
201 if (packager.platform === index_1.Platform.WINDOWS && publishConfig.publisherName == null) {
202 const winPackager = packager;
203 const publisherName = winPackager.isForceCodeSigningVerification ? await winPackager.computedPublisherName.value : undefined;
204 if (publisherName != null) {
205 publishConfig.publisherName = publisherName;
206 }
207 }
208 return publishConfig;
209}
210exports.getAppUpdatePublishConfiguration = getAppUpdatePublishConfiguration;
211async function getPublishConfigsForUpdateInfo(packager, publishConfigs, arch) {
212 if (publishConfigs === null) {
213 return null;
214 }
215 if (publishConfigs.length === 0) {
216 builder_util_1.log.debug(null, "getPublishConfigsForUpdateInfo: no publishConfigs, detect using repository info");
217 // https://github.com/electron-userland/electron-builder/issues/925#issuecomment-261732378
218 // default publish config is github, file should be generated regardless of publish state (user can test installer locally or manage the release process manually)
219 const repositoryInfo = await packager.info.repositoryInfo;
220 debug(`getPublishConfigsForUpdateInfo: ${(0, builder_util_1.safeStringifyJson)(repositoryInfo)}`);
221 if (repositoryInfo != null && repositoryInfo.type === "github") {
222 const resolvedPublishConfig = await getResolvedPublishConfig(packager, packager.info, { provider: repositoryInfo.type }, arch, false);
223 if (resolvedPublishConfig != null) {
224 debug(`getPublishConfigsForUpdateInfo: resolve to publish config ${(0, builder_util_1.safeStringifyJson)(resolvedPublishConfig)}`);
225 return [resolvedPublishConfig];
226 }
227 }
228 }
229 return publishConfigs;
230}
231exports.getPublishConfigsForUpdateInfo = getPublishConfigsForUpdateInfo;
232function createPublisher(context, version, publishConfig, options, packager) {
233 if (debug.enabled) {
234 debug(`Create publisher: ${(0, builder_util_1.safeStringifyJson)(publishConfig)}`);
235 }
236 const provider = publishConfig.provider;
237 switch (provider) {
238 case "github":
239 return new gitHubPublisher_1.GitHubPublisher(context, publishConfig, version, options);
240 case "keygen":
241 return new KeygenPublisher_1.KeygenPublisher(context, publishConfig, version);
242 case "snapStore":
243 return new SnapStorePublisher_1.SnapStorePublisher(context, publishConfig);
244 case "generic":
245 return null;
246 default: {
247 const clazz = requireProviderClass(provider, packager);
248 return clazz == null ? null : new clazz(context, publishConfig);
249 }
250 }
251}
252exports.createPublisher = createPublisher;
253function requireProviderClass(provider, packager) {
254 switch (provider) {
255 case "github":
256 return gitHubPublisher_1.GitHubPublisher;
257 case "generic":
258 return null;
259 case "keygen":
260 return KeygenPublisher_1.KeygenPublisher;
261 case "s3":
262 return s3Publisher_1.default;
263 case "snapStore":
264 return SnapStorePublisher_1.SnapStorePublisher;
265 case "spaces":
266 return spacesPublisher_1.default;
267 case "bitbucket":
268 return BitbucketPublisher_1.BitbucketPublisher;
269 default: {
270 const name = `electron-publisher-${provider}`;
271 let module = null;
272 try {
273 module = require(path.join(packager.buildResourcesDir, name + ".js"));
274 }
275 catch (ignored) {
276 console.log(ignored);
277 }
278 if (module == null) {
279 module = require(name);
280 }
281 return module.default || module;
282 }
283 }
284}
285function computeDownloadUrl(publishConfiguration, fileName, packager) {
286 if (publishConfiguration.provider === "generic") {
287 const baseUrlString = publishConfiguration.url;
288 if (fileName == null) {
289 return baseUrlString;
290 }
291 const baseUrl = url.parse(baseUrlString);
292 return url.format({ ...baseUrl, pathname: path.posix.resolve(baseUrl.pathname || "/", encodeURI(fileName)) });
293 }
294 let baseUrl;
295 if (publishConfiguration.provider === "github") {
296 const gh = publishConfiguration;
297 baseUrl = `${(0, builder_util_runtime_1.githubUrl)(gh)}/${gh.owner}/${gh.repo}/releases/download/${gh.vPrefixedTagName === false ? "" : "v"}${packager.appInfo.version}`;
298 }
299 else {
300 baseUrl = (0, builder_util_runtime_1.getS3LikeProviderBaseUrl)(publishConfiguration);
301 }
302 if (fileName == null) {
303 return baseUrl;
304 }
305 return `${baseUrl}/${encodeURI(fileName)}`;
306}
307exports.computeDownloadUrl = computeDownloadUrl;
308async function getPublishConfigs(platformPackager, targetSpecificOptions, arch, errorIfCannot) {
309 let publishers;
310 // check build.nsis (target)
311 if (targetSpecificOptions != null) {
312 publishers = targetSpecificOptions.publish;
313 // if explicitly set to null - do not publish
314 if (publishers === null) {
315 return null;
316 }
317 }
318 // check build.win (platform)
319 if (publishers == null) {
320 publishers = platformPackager.platformSpecificBuildOptions.publish;
321 if (publishers === null) {
322 return null;
323 }
324 }
325 if (publishers == null) {
326 publishers = platformPackager.config.publish;
327 if (publishers === null) {
328 return null;
329 }
330 }
331 return await resolvePublishConfigurations(publishers, platformPackager, platformPackager.info, arch, errorIfCannot);
332}
333exports.getPublishConfigs = getPublishConfigs;
334async function resolvePublishConfigurations(publishers, platformPackager, packager, arch, errorIfCannot) {
335 if (publishers == null) {
336 let serviceName = null;
337 if (!(0, builder_util_1.isEmptyOrSpaces)(process.env.GH_TOKEN) || !(0, builder_util_1.isEmptyOrSpaces)(process.env.GITHUB_TOKEN)) {
338 serviceName = "github";
339 }
340 else if (!(0, builder_util_1.isEmptyOrSpaces)(process.env.KEYGEN_TOKEN)) {
341 serviceName = "keygen";
342 }
343 else if (!(0, builder_util_1.isEmptyOrSpaces)(process.env.BITBUCKET_TOKEN)) {
344 serviceName = "bitbucket";
345 }
346 else if (!(0, builder_util_1.isEmptyOrSpaces)(process.env.BT_TOKEN)) {
347 throw new Error("Bintray has been sunset and is no longer supported by electron-builder. Ref: https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/");
348 }
349 if (serviceName != null) {
350 builder_util_1.log.debug(null, `detect ${serviceName} as publish provider`);
351 return [(await getResolvedPublishConfig(platformPackager, packager, { provider: serviceName }, arch, errorIfCannot))];
352 }
353 }
354 if (publishers == null) {
355 return [];
356 }
357 debug(`Explicit publish provider: ${(0, builder_util_1.safeStringifyJson)(publishers)}`);
358 return await bluebird_lst_1.default.map((0, builder_util_1.asArray)(publishers), it => getResolvedPublishConfig(platformPackager, packager, typeof it === "string" ? { provider: it } : it, arch, errorIfCannot));
359}
360function isSuitableWindowsTarget(target) {
361 if (target.name === "appx" && target.options != null && target.options.electronUpdaterAware) {
362 return true;
363 }
364 return target.name === "nsis" || target.name.startsWith("nsis-");
365}
366function expandPublishConfig(options, platformPackager, packager, arch) {
367 for (const name of Object.keys(options)) {
368 const value = options[name];
369 if (typeof value === "string") {
370 const archValue = arch == null ? null : builder_util_1.Arch[arch];
371 const expanded = platformPackager == null ? (0, macroExpander_1.expandMacro)(value, archValue, packager.appInfo) : platformPackager.expandMacro(value, archValue);
372 if (expanded !== value) {
373 options[name] = expanded;
374 }
375 }
376 }
377}
378function isDetectUpdateChannel(platformSpecificConfiguration, configuration) {
379 const value = platformSpecificConfiguration == null ? null : platformSpecificConfiguration.detectUpdateChannel;
380 return value == null ? configuration.detectUpdateChannel !== false : value;
381}
382async function getResolvedPublishConfig(platformPackager, packager, options, arch, errorIfCannot) {
383 options = { ...options };
384 expandPublishConfig(options, platformPackager, packager, arch);
385 let channelFromAppVersion = null;
386 if (options.channel == null &&
387 isDetectUpdateChannel(platformPackager == null ? null : platformPackager.platformSpecificBuildOptions, packager.config)) {
388 channelFromAppVersion = packager.appInfo.channel;
389 }
390 const provider = options.provider;
391 if (provider === "generic") {
392 const o = options;
393 if (o.url == null) {
394 throw new builder_util_1.InvalidConfigurationError(`Please specify "url" for "generic" update server`);
395 }
396 if (channelFromAppVersion != null) {
397 ;
398 o.channel = channelFromAppVersion;
399 }
400 return options;
401 }
402 const providerClass = requireProviderClass(options.provider, packager);
403 if (providerClass != null && providerClass.checkAndResolveOptions != null) {
404 await providerClass.checkAndResolveOptions(options, channelFromAppVersion, errorIfCannot);
405 return options;
406 }
407 if (provider === "keygen") {
408 return {
409 ...options,
410 platform: platformPackager === null || platformPackager === void 0 ? void 0 : platformPackager.platform.name,
411 };
412 }
413 const isGithub = provider === "github";
414 if (!isGithub && provider !== "bitbucket") {
415 return options;
416 }
417 let owner = isGithub ? options.owner : options.owner;
418 let project = isGithub ? options.repo : options.slug;
419 if (isGithub && owner == null && project != null) {
420 const index = project.indexOf("/");
421 if (index > 0) {
422 const repo = project;
423 project = repo.substring(0, index);
424 owner = repo.substring(index + 1);
425 }
426 }
427 async function getInfo() {
428 const info = await packager.repositoryInfo;
429 if (info != null) {
430 return info;
431 }
432 const message = `Cannot detect repository by .git/config. Please specify "repository" in the package.json (https://docs.npmjs.com/files/package.json#repository).\nPlease see https://electron.build/configuration/publish`;
433 if (errorIfCannot) {
434 throw new Error(message);
435 }
436 else {
437 builder_util_1.log.warn(message);
438 return null;
439 }
440 }
441 if (!owner || !project) {
442 builder_util_1.log.debug({ reason: "owner or project is not specified explicitly", provider, owner, project }, "calling getInfo");
443 const info = await getInfo();
444 if (info == null) {
445 return null;
446 }
447 if (!owner) {
448 owner = info.user;
449 }
450 if (!project) {
451 project = info.project;
452 }
453 }
454 if (isGithub) {
455 if (options.token != null && !options.private) {
456 builder_util_1.log.warn('"token" specified in the github publish options. It should be used only for [setFeedURL](module:electron-updater/out/AppUpdater.AppUpdater+setFeedURL).');
457 }
458 //tslint:disable-next-line:no-object-literal-type-assertion
459 return { owner, repo: project, ...options };
460 }
461 else {
462 //tslint:disable-next-line:no-object-literal-type-assertion
463 return { owner, slug: project, ...options };
464 }
465}
466//# sourceMappingURL=PublishManager.js.map
\No newline at end of file