From a94532164709a545c0f6551fdc336dbc5377bda8 Mon Sep 17 00:00:00 2001
From: Mike Maietta
Date: Wed, 8 Sep 2021 10:03:06 -0700
Subject: [PATCH] feat: adding Bitbucket publisher and autoupdater (#6228)
---
.changeset/shiny-colts-behave.md | 9 +
docs/api/electron-builder.md | 4 +-
docs/configuration/publish.md | 117 +++++++++---
docs/generated/DebOptions.md | 2 +-
docs/generated/bitbucket-options.md | 16 ++
docs/generated/s3-options.md | 5 -
docs/generated/snap-store-options.md | 17 --
docs/generated/spaces-options.md | 16 --
packages/app-builder-lib/package.json | 1 +
packages/app-builder-lib/scheme.json | 171 ++++++++++++++++++
.../src/options/linuxOptions.ts | 3 +-
.../src/publish/BitbucketPublisher.ts | 67 +++++++
.../src/publish/KeygenPublisher.ts | 2 +-
.../src/publish/PublishManager.ts | 12 +-
.../builder-util-runtime/src/httpExecutor.ts | 8 +-
packages/builder-util-runtime/src/index.ts | 1 +
.../src/publishOptions.ts | 69 ++++++-
packages/electron-publish/src/publisher.ts | 4 +-
.../electron-updater/src/providerFactory.ts | 5 +
.../src/providers/BitbucketProvider.ts | 43 +++++
pnpm-lock.yaml | 11 ++
scripts/jsdoc2md2html.js | 3 +-
test/src/ArtifactPublisherTest.ts | 13 +-
test/src/helpers/updaterTestUtil.ts | 14 +-
test/src/updater/nsisUpdaterTest.ts | 15 +-
25 files changed, 534 insertions(+), 94 deletions(-)
create mode 100644 .changeset/shiny-colts-behave.md
create mode 100644 docs/generated/bitbucket-options.md
delete mode 100644 docs/generated/snap-store-options.md
delete mode 100644 docs/generated/spaces-options.md
create mode 100644 packages/app-builder-lib/src/publish/BitbucketPublisher.ts
create mode 100644 packages/electron-updater/src/providers/BitbucketProvider.ts
diff --git a/.changeset/shiny-colts-behave.md b/.changeset/shiny-colts-behave.md
new file mode 100644
index 00000000000..ddb4be564be
--- /dev/null
+++ b/.changeset/shiny-colts-behave.md
@@ -0,0 +1,9 @@
+---
+"app-builder-lib": minor
+"builder-util-runtime": minor
+"builder-util": minor
+"electron-publish": minor
+"electron-updater": minor
+---
+
+feat: adding Bitbucket publisher and autoupdater
diff --git a/docs/api/electron-builder.md b/docs/api/electron-builder.md
index 0965c871778..0128cf42c2a 100644
--- a/docs/api/electron-builder.md
+++ b/docs/api/electron-builder.md
@@ -1709,7 +1709,7 @@ return path.join(target.outDir, __${target.name}-${getArtifactArchName(arc
options |
-PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | String |
+PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String |
If you want to override configuration in the app-update.yml . |
@@ -1834,7 +1834,7 @@ This is different from the normal quit event sequence.
options |
-PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | String |
+PublishConfiguration | String | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | module:builder-util-runtime/out/publishOptions.CustomPublishOptions | module:builder-util-runtime/out/publishOptions.KeygenOptions | SnapStoreOptions | module:builder-util-runtime/out/publishOptions.BitbucketOptions | String |
If you want to override configuration in the app-update.yml . |
diff --git a/docs/configuration/publish.md b/docs/configuration/publish.md
index 1f32c3d6869..a622f50e19d 100644
--- a/docs/configuration/publish.md
+++ b/docs/configuration/publish.md
@@ -12,23 +12,23 @@ If `KEYGEN_TOKEN` is defined and `GH_TOKEN` or `GITHUB_TOKEN` is not — default
!!! info "Snap store"
`snap` target by default publishes to snap store (the app store for Linux). To force publishing to another providers, explicitly specify publish configuration for `snap`.
-You can publish to multiple providers. For example, to publish Windows artifacts to both GitHub and Bintray (order is important — first item will be used as a default auto-update server, so, in this example app will use github as auto-update provider):
+You can publish to multiple providers. For example, to publish Windows artifacts to both GitHub and Bitbucket (order is important — first item will be used as a default auto-update server, so, in this example app will use github as auto-update provider):
-```json tab="package.json"
+```json
{
"build": {
"win": {
- "publish": ["github", "bintray"]
+ "publish": ["github", "bitbucket"]
}
}
}
```
-```yaml tab="electron-builder.yaml"
+```yaml
win:
publish:
- github
- - bintray
+ - bitbucket
```
You can also configure publishing using CLI arguments, for example, to force publishing snap not to Snap Store, but to GitHub: `-c.snap.publish=github`
@@ -66,7 +66,7 @@ But please consider using automatic rules instead of explicitly specifying `publ
Add to `scripts` in the development `package.json`:
- ```json tab="package.json"
+ ```json
"release": "electron-builder"
```
@@ -92,7 +92,7 @@ This example workflow is modelled on how releases are handled in maven (it is an
3. When you are ready to deploy, simply change you package version to `1.9.0` and push. This will then produce a `latest.yml` and `something.exe` on s3. Usually you'll git-tag this version as well (just to keep track of it).
4. Change the version back to a snapshot version right after, i.e. `1.10.0-snapshot`, and commit it.
-## GitHub Repository and Bintray Package
+## GitHub Repository
Detected automatically using:
@@ -103,6 +103,16 @@ Detected automatically using:
* or `CIRCLE_PROJECT_USERNAME`/`CIRCLE_PROJECT_REPONAME`,
* if no env, from `.git/config` origin url.
+## Publishers
+**Options Available:**
+- GenericServerOptions
+- GithubOptions
+- SnapStoreOptions
+- SpacesOptions
+- KeygenOptions
+- BitbucketOptions
+- S3Options
+
GenericServerOptions
Generic (any HTTP(S) server) options.
@@ -216,27 +226,76 @@ Define KEYGEN_TOKEN
environment variable.
module:http.OutgoingHttpHeaders - Any custom request headers
-
-
-
-## S3Options
-[Amazon S3](https://aws.amazon.com/s3/) options.
-
-AWS credentials are required, please see [getting your credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html).
-Define `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [environment variables](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html).
-Or in the [~/.aws/credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html).
-
-Example configuration:
-
-```json tab="package.json"
-{
- "build":
- "publish": {
- "provider": "s3",
- "bucket": "bucket-name"
- }
- }
+BitbucketOptions
+Bitbucket options.
+https://bitbucket.org/
+Define BITBUCKET_TOKEN
environment variable.
+For converting an app password to a usable token, you can utilize this
+convertAppPassword(owner: string, token: string) {
+const base64encodedData = Buffer.from(`${owner}:${token.trim()}`).toString("base64")
+return `Basic ${base64encodedData}`
}
-```
+
+
+provider
“bitbucket” - The provider. Must be bitbucket
.
+owner
String - Repository owner
+slug
String - Repository slug/name
+channel
= latest
String | “undefined” - The channel.
+
+Inherited from PublishConfiguration
:
+
+-
+
publishAutoUpdate
= true
Boolean - Whether to publish auto update info files.
+Auto update relies only on the first provider in the list (you can specify several publishers). Thus, probably, there`s no need to upload the metadata files for the other configured providers. But by default will be uploaded.
+
+-
+
module:http.OutgoingHttpHeaders - Any custom request headers
+
+
+S3Options
+Amazon S3 options.
+AWS credentials are required, please see getting your credentials.
+Define AWS_ACCESS_KEY_ID
and AWS_SECRET_ACCESS_KEY
environment variables.
+Or in the ~/.aws/credentials.
+Example configuration:
+{
+"build":
+"publish": {
+"provider": "s3",
+"bucket": "bucket-name"
+}
+}
+}
+
+
+-
+
provider
“s3” - The provider. Must be s3
.
+
+-
+
bucket
String - The bucket name.
+
+-
+
region
String | “undefined” - The region. Is determined and set automatically when publishing.
+
+-
+
acl
= public-read
“private” | “public-read” | “undefined” - The ACL. Set to null
to not add.
+Please see required permissions for the S3 provider.
+
+-
+
storageClass
= STANDARD
“STANDARD” | “REDUCED_REDUNDANCY” | “STANDARD_IA” | “undefined” - The type of storage to use for the object.
+
+-
+
encryption
“AES256” | “aws:kms” | “undefined” - Server-side encryption algorithm to use for the object.
+
+-
+
endpoint
String | “undefined” - The endpoint URI to send requests to. The default endpoint is built from the configured region. The endpoint should be a string like https://{service}.{region}.amazonaws.com
.
+
+-
+
channel
= latest
String | “undefined” - The update channel.
+
+-
+
path
= /
String | “undefined” - The directory path.
+
+
-{!generated/s3-options.md!}
+
diff --git a/docs/generated/DebOptions.md b/docs/generated/DebOptions.md
index d1d6d554db6..88f211290b7 100644
--- a/docs/generated/DebOptions.md
+++ b/docs/generated/DebOptions.md
@@ -1,5 +1,5 @@
-depends
Array<String> | “undefined” - Package dependencies. Defaults to ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]
. If need to support Debian, libappindicator1
should be removed, deprecated in Debian. If need to support KDE, gconf2
and gconf-service
should be removed, for GNOME and no longer used by GNOME.
+depends
Array<String> | “undefined” - Package dependencies. Defaults to ["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]
. If need to support Debian, libappindicator1
should be removed, it is deprecated in Debian. If need to support KDE, gconf2
and gconf-service
should be removed as it’s no longer used by GNOME](https://packages.debian.org/bullseye/gconf2).
packageCategory
String | “undefined” - The package category.
priority
String | “undefined” - The Priority attribute.
diff --git a/docs/generated/bitbucket-options.md b/docs/generated/bitbucket-options.md
new file mode 100644
index 00000000000..467979583e7
--- /dev/null
+++ b/docs/generated/bitbucket-options.md
@@ -0,0 +1,16 @@
+
+provider
“bitbucket” - The provider. Must be bitbucket
.
+owner
String - Repository owner
+slug
String - Repository slug/name
+channel
= latest
String | “undefined” - The channel.
+
+Inherited from PublishConfiguration
:
+
+-
+
publishAutoUpdate
= true
Boolean - Whether to publish auto update info files.
+Auto update relies only on the first provider in the list (you can specify several publishers). Thus, probably, there`s no need to upload the metadata files for the other configured providers. But by default will be uploaded.
+
+-
+
module:http.OutgoingHttpHeaders - Any custom request headers
+
+
diff --git a/docs/generated/s3-options.md b/docs/generated/s3-options.md
index db794029f3c..6efd2230d0a 100644
--- a/docs/generated/s3-options.md
+++ b/docs/generated/s3-options.md
@@ -1,6 +1,3 @@
-
-
-
-
-
diff --git a/docs/generated/snap-store-options.md b/docs/generated/snap-store-options.md
deleted file mode 100644
index 059c1c051ee..00000000000
--- a/docs/generated/snap-store-options.md
+++ /dev/null
@@ -1,17 +0,0 @@
-SnapStoreOptions
-Snap Store options.
-
-provider
“snapStore” - The provider. Must be snapStore
.
-repo
String - snapcraft repo name
-channels
= ["edge"]
String | Array<String> | “undefined” - The list of channels the snap would be released.
-
-Inherited from PublishConfiguration
:
-
--
-
publishAutoUpdate
= true
Boolean - Whether to publish auto update info files.
-Auto update relies only on the first provider in the list (you can specify several publishers). Thus, probably, there`s no need to upload the metadata files for the other configured providers. But by default will be uploaded.
-
--
-
module:http.OutgoingHttpHeaders - Any custom request headers
-
-
diff --git a/docs/generated/spaces-options.md b/docs/generated/spaces-options.md
deleted file mode 100644
index 43220861e10..00000000000
--- a/docs/generated/spaces-options.md
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-SpacesOptions
-DigitalOcean Spaces options.
-Access key is required, define DO_KEY_ID
and DO_SECRET_KEY
environment variables.
-
-provider
“spaces” - The provider. Must be spaces
.
-name
String - The space name.
-region
String - The region (e.g. nyc3
).
-channel
= latest
String | “undefined” - The update channel.
-path
= /
String | “undefined” - The directory path.
-acl
= public-read
“private” | “public-read” | “undefined” - The ACL. Set to null
to not add.
-
-
-
diff --git a/packages/app-builder-lib/package.json b/packages/app-builder-lib/package.json
index 6df0c86a735..3f527596aaa 100644
--- a/packages/app-builder-lib/package.json
+++ b/packages/app-builder-lib/package.json
@@ -60,6 +60,7 @@
"ejs": "^3.1.6",
"electron-osx-sign": "^0.5.0",
"electron-publish": "workspace:*",
+ "form-data": "^4.0.0",
"fs-extra": "^10.0.0",
"hosted-git-info": "^4.0.2",
"is-ci": "^3.0.0",
diff --git a/packages/app-builder-lib/scheme.json b/packages/app-builder-lib/scheme.json
index ca9d4f3f612..305817cffcc 100644
--- a/packages/app-builder-lib/scheme.json
+++ b/packages/app-builder-lib/scheme.json
@@ -90,6 +90,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -117,6 +120,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -246,6 +252,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -273,6 +282,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -432,6 +444,69 @@
],
"type": "object"
},
+ "BitbucketOptions": {
+ "additionalProperties": false,
+ "description": "Bitbucket options.\nhttps://keygen.sh/\nDefine `BITBUCKET_TOKEN` environment variable.",
+ "properties": {
+ "channel": {
+ "default": "latest",
+ "description": "The channel.",
+ "type": [
+ "null",
+ "string"
+ ]
+ },
+ "owner": {
+ "description": "Repository owner",
+ "type": "string"
+ },
+ "provider": {
+ "description": "The provider. Must be `bitbucket`.",
+ "enum": [
+ "bitbucket"
+ ],
+ "type": "string"
+ },
+ "publishAutoUpdate": {
+ "default": true,
+ "description": "Whether to publish auto update info files.\n\nAuto update relies only on the first provider in the list (you can specify several publishers).\nThus, probably, there`s no need to upload the metadata files for the other configured providers. But by default will be uploaded.",
+ "type": "boolean"
+ },
+ "publisherName": {
+ "anyOf": [
+ {
+ "items": {
+ "type": "string"
+ },
+ "type": "array"
+ },
+ {
+ "type": "null"
+ }
+ ]
+ },
+ "requestHeaders": {
+ "$ref": "#/definitions/OutgoingHttpHeaders",
+ "description": "Any custom request headers"
+ },
+ "slug": {
+ "description": "Repository slug/name",
+ "type": "string"
+ },
+ "updaterCacheDirName": {
+ "type": [
+ "null",
+ "string"
+ ]
+ }
+ },
+ "required": [
+ "owner",
+ "provider",
+ "slug"
+ ],
+ "type": "object"
+ },
"CustomNsisBinary": {
"additionalProperties": false,
"properties": {
@@ -678,6 +753,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -705,6 +783,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -868,6 +949,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -895,6 +979,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -1247,6 +1334,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -1274,6 +1364,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -1899,6 +1992,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -1926,6 +2022,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -2151,6 +2250,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -2178,6 +2280,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -2645,6 +2750,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -2672,6 +2780,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -3250,6 +3361,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -3277,6 +3391,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -3530,6 +3647,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -3557,6 +3677,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -3818,6 +3941,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -3845,6 +3971,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -4142,6 +4271,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -4169,6 +4301,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -4463,6 +4598,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -4490,6 +4628,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -4597,6 +4738,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -4624,6 +4768,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -5119,6 +5266,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -5146,6 +5296,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -5442,6 +5595,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -5469,6 +5625,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -5839,6 +5998,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -5866,6 +6028,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
@@ -6738,6 +6903,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"items": {
"anyOf": [
@@ -6765,6 +6933,9 @@
{
"$ref": "#/definitions/SnapStoreOptions"
},
+ {
+ "$ref": "#/definitions/BitbucketOptions"
+ },
{
"type": "string"
}
diff --git a/packages/app-builder-lib/src/options/linuxOptions.ts b/packages/app-builder-lib/src/options/linuxOptions.ts
index 1f2e5b225a0..8b84e140c70 100644
--- a/packages/app-builder-lib/src/options/linuxOptions.ts
+++ b/packages/app-builder-lib/src/options/linuxOptions.ts
@@ -104,10 +104,11 @@ export interface LinuxTargetSpecificOptions extends CommonLinuxOptions, TargetSp
*/
readonly fpm?: Array | null
}
-
export interface DebOptions extends LinuxTargetSpecificOptions {
/**
* Package dependencies. Defaults to `["gconf2", "gconf-service", "libnotify4", "libappindicator1", "libxtst6", "libnss3"]`.
+ * If need to support Debian, `libappindicator1` should be removed, it is [deprecated in Debian](https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=895037).
+ * If need to support KDE, `gconf2` and `gconf-service` should be removed as it's no longer used by GNOME](https://packages.debian.org/bullseye/gconf2).
*/
readonly depends?: Array | null
diff --git a/packages/app-builder-lib/src/publish/BitbucketPublisher.ts b/packages/app-builder-lib/src/publish/BitbucketPublisher.ts
new file mode 100644
index 00000000000..78ba04c2b43
--- /dev/null
+++ b/packages/app-builder-lib/src/publish/BitbucketPublisher.ts
@@ -0,0 +1,67 @@
+import { Arch, InvalidConfigurationError, isEmptyOrSpaces } from "builder-util"
+import { httpExecutor } from "builder-util/out/nodeHttpExecutor"
+import { ClientRequest, RequestOptions } from "http"
+import { HttpPublisher, PublishContext } from "electron-publish"
+import { BitbucketOptions } from "builder-util-runtime/out/publishOptions"
+import { configureRequestOptions, HttpExecutor } from "builder-util-runtime"
+import * as FormData from "form-data"
+import { readFile } from "fs/promises"
+export class BitbucketPublisher extends HttpPublisher {
+ readonly providerName = "bitbucket"
+ readonly hostname = "api.bitbucket.org"
+
+ private readonly info: BitbucketOptions
+ private readonly auth: string
+ private readonly basePath: string
+
+ constructor(context: PublishContext, info: BitbucketOptions) {
+ super(context)
+
+ const token = process.env.BITBUCKET_TOKEN
+ if (isEmptyOrSpaces(token)) {
+ throw new InvalidConfigurationError(`Bitbucket token is not set using env "BITBUCKET_TOKEN" (see https://www.electron.build/configuration/publish#BitbucketOptions)`)
+ }
+ this.info = info
+ this.auth = BitbucketPublisher.convertAppPassword(this.info.owner, token)
+ this.basePath = `/2.0/repositories/${this.info.owner}/${this.info.slug}/downloads`
+ }
+
+ protected doUpload(
+ fileName: string,
+ _arch: Arch,
+ _dataLength: number,
+ _requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void,
+ file: string
+ ): Promise {
+ return HttpExecutor.retryOnServerError(async () => {
+ const fileContent = await readFile(file)
+ const form = new FormData()
+ form.append("files", fileContent, fileName)
+ const upload: RequestOptions = {
+ hostname: this.hostname,
+ path: this.basePath,
+ headers: form.getHeaders(),
+ }
+ await httpExecutor.doApiRequest(configureRequestOptions(upload, this.auth, "POST"), this.context.cancellationToken, it => form.pipe(it))
+ return fileName
+ })
+ }
+
+ async deleteRelease(filename: string): Promise {
+ const req: RequestOptions = {
+ hostname: this.hostname,
+ path: `${this.basePath}/${filename}`,
+ }
+ await httpExecutor.request(configureRequestOptions(req, this.auth, "DELETE"), this.context.cancellationToken)
+ }
+
+ toString() {
+ const { owner, slug, channel } = this.info
+ return `Bitbucket (owner: ${owner}, slug: ${slug}, channel: ${channel})`
+ }
+
+ static convertAppPassword(owner: string, token: string) {
+ const base64encodedData = Buffer.from(`${owner}:${token.trim()}`).toString("base64")
+ return `Basic ${base64encodedData}`
+ }
+}
diff --git a/packages/app-builder-lib/src/publish/KeygenPublisher.ts b/packages/app-builder-lib/src/publish/KeygenPublisher.ts
index 9dde27710e1..3ad121a4f7a 100644
--- a/packages/app-builder-lib/src/publish/KeygenPublisher.ts
+++ b/packages/app-builder-lib/src/publish/KeygenPublisher.ts
@@ -35,7 +35,7 @@ export class KeygenPublisher extends HttpPublisher {
dataLength: number,
requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- _file?: string
+ _file: string
): Promise {
return HttpExecutor.retryOnServerError(async () => {
const { data, errors } = await this.upsertRelease(fileName, dataLength)
diff --git a/packages/app-builder-lib/src/publish/PublishManager.ts b/packages/app-builder-lib/src/publish/PublishManager.ts
index 6c299224336..7a95f4a99b5 100644
--- a/packages/app-builder-lib/src/publish/PublishManager.ts
+++ b/packages/app-builder-lib/src/publish/PublishManager.ts
@@ -32,6 +32,7 @@ import { WinPackager } from "../winPackager"
import { SnapStorePublisher } from "./SnapStorePublisher"
import { createUpdateInfoTasks, UpdateInfoFileTask, writeUpdateInfoFiles } from "./updateInfoBuilder"
import { KeygenPublisher } from "./KeygenPublisher"
+import { BitbucketPublisher } from "./BitbucketPublisher"
const 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)" +
@@ -303,12 +304,12 @@ export function createPublisher(context: PublishContext, version: string, publis
case "keygen":
return new KeygenPublisher(context, publishConfig as KeygenOptions, version)
- case "generic":
- return null
-
case "snapStore":
return new SnapStorePublisher(context, publishConfig as SnapStoreOptions)
+ case "generic":
+ return null
+
default: {
const clazz = requireProviderClass(provider, packager)
return clazz == null ? null : new clazz(context, publishConfig)
@@ -339,6 +340,9 @@ function requireProviderClass(provider: string, packager: Packager): any | null
case "spaces":
return SpacesPublisher
+ case "bitbucket":
+ return BitbucketPublisher
+
default: {
const name = `electron-publisher-${provider}`
let module: any = null
@@ -430,6 +434,8 @@ async function resolvePublishConfigurations(
serviceName = "bintray"
} else if (!isEmptyOrSpaces(process.env.KEYGEN_TOKEN)) {
serviceName = "keygen"
+ } else if (!isEmptyOrSpaces(process.env.BITBUCKET_TOKEN)) {
+ serviceName = "bitbucket"
}
if (serviceName != null) {
diff --git a/packages/builder-util-runtime/src/httpExecutor.ts b/packages/builder-util-runtime/src/httpExecutor.ts
index 5a89e018d5c..7a304649e7e 100644
--- a/packages/builder-util-runtime/src/httpExecutor.ts
+++ b/packages/builder-util-runtime/src/httpExecutor.ts
@@ -72,7 +72,7 @@ export function parseJson(result: Promise) {
interface Request {
abort: () => void
- end: () => void
+ end: (data?: Buffer) => void
}
export abstract class HttpExecutor {
protected readonly maxRedirects = 10
@@ -94,9 +94,7 @@ export abstract class HttpExecutor {
...opts,
}
}
- return this.doApiRequest(options, cancellationToken, it => {
- ;(it as any).end(encodedData)
- })
+ return this.doApiRequest(options, cancellationToken, it => it.end(encodedData))
}
doApiRequest(
@@ -499,7 +497,7 @@ function configurePipes(options: DownloadCallOptions, response: IncomingMessage)
})
}
-export function configureRequestOptions(options: RequestOptions, token?: string | null, method?: "GET" | "DELETE" | "PUT"): RequestOptions {
+export function configureRequestOptions(options: RequestOptions, token?: string | null, method?: "GET" | "DELETE" | "PUT" | "POST"): RequestOptions {
if (method != null) {
options.method = method
}
diff --git a/packages/builder-util-runtime/src/index.ts b/packages/builder-util-runtime/src/index.ts
index 4b148906508..4893a9fb1d3 100644
--- a/packages/builder-util-runtime/src/index.ts
+++ b/packages/builder-util-runtime/src/index.ts
@@ -19,6 +19,7 @@ export {
GenericServerOptions,
GithubOptions,
KeygenOptions,
+ BitbucketOptions,
SnapStoreOptions,
PublishConfiguration,
S3Options,
diff --git a/packages/builder-util-runtime/src/publishOptions.ts b/packages/builder-util-runtime/src/publishOptions.ts
index bdacaa9f821..847865ad589 100644
--- a/packages/builder-util-runtime/src/publishOptions.ts
+++ b/packages/builder-util-runtime/src/publishOptions.ts
@@ -1,9 +1,19 @@
import { OutgoingHttpHeaders } from "http"
-export type PublishProvider = "github" | "bintray" | "s3" | "spaces" | "generic" | "custom" | "snapStore" | "keygen"
+export type PublishProvider = "github" | "bintray" | "s3" | "spaces" | "generic" | "custom" | "snapStore" | "keygen" | "bitbucket"
// typescript-json-schema generates only PublishConfiguration if it is specified in the list, so, it is not added here
-export type AllPublishOptions = string | GithubOptions | S3Options | SpacesOptions | GenericServerOptions | BintrayOptions | CustomPublishOptions | KeygenOptions | SnapStoreOptions
+export type AllPublishOptions =
+ | string
+ | GithubOptions
+ | S3Options
+ | SpacesOptions
+ | GenericServerOptions
+ | BintrayOptions
+ | CustomPublishOptions
+ | KeygenOptions
+ | SnapStoreOptions
+ | BitbucketOptions
export interface PublishConfiguration {
/**
@@ -179,6 +189,42 @@ export interface KeygenOptions extends PublishConfiguration {
readonly platform?: string | null
}
+/**
+ * Bitbucket options.
+ * https://bitbucket.org/
+ * Define `BITBUCKET_TOKEN` environment variable.
+ *
+ * For converting an app password to a usable token, you can utilize this
+```typescript
+convertAppPassword(owner: string, token: string) {
+ const base64encodedData = Buffer.from(`${owner}:${token.trim()}`).toString("base64")
+ return `Basic ${base64encodedData}`
+}
+```
+ */
+export interface BitbucketOptions extends PublishConfiguration {
+ /**
+ * The provider. Must be `bitbucket`.
+ */
+ readonly provider: "bitbucket"
+
+ /**
+ * Repository owner
+ */
+ readonly owner: string
+
+ /**
+ * Repository slug/name
+ */
+ readonly slug: string
+
+ /**
+ * The channel.
+ * @default latest
+ */
+ readonly channel?: string | null
+}
+
/**
* [Snap Store](https://snapcraft.io/) options.
*/
@@ -221,6 +267,25 @@ export interface BaseS3Options extends PublishConfiguration {
readonly acl?: "private" | "public-read" | null
}
+/**
+ * [Amazon S3](https://aws.amazon.com/s3/) options.
+ * AWS credentials are required, please see [getting your credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/getting-your-credentials.html).
+ * Define `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` [environment variables](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-environment.html).
+ * Or in the [~/.aws/credentials](http://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html).
+ *
+ * Example configuration:
+ *
+```json
+{
+ "build":
+ "publish": {
+ "provider": "s3",
+ "bucket": "bucket-name"
+ }
+ }
+}
+```
+ */
export interface S3Options extends BaseS3Options {
/**
* The provider. Must be `s3`.
diff --git a/packages/electron-publish/src/publisher.ts b/packages/electron-publish/src/publisher.ts
index 4dc73a967bf..3b2537c95c5 100644
--- a/packages/electron-publish/src/publisher.ts
+++ b/packages/electron-publish/src/publisher.ts
@@ -77,7 +77,7 @@ export abstract class HttpPublisher extends Publisher {
const fileName = (this.useSafeArtifactName ? task.safeArtifactName : null) || basename(task.file)
if (task.fileContent != null) {
- await this.doUpload(fileName, task.arch || Arch.x64, task.fileContent.length, it => it.end(task.fileContent))
+ await this.doUpload(fileName, task.arch || Arch.x64, task.fileContent.length, it => it.end(task.fileContent), task.file)
return
}
@@ -104,7 +104,7 @@ export abstract class HttpPublisher extends Publisher {
arch: Arch,
dataLength: number,
requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void,
- file?: string
+ file: string
): Promise
}
diff --git a/packages/electron-updater/src/providerFactory.ts b/packages/electron-updater/src/providerFactory.ts
index 87e3d4c8e01..a9f7e93963c 100644
--- a/packages/electron-updater/src/providerFactory.ts
+++ b/packages/electron-updater/src/providerFactory.ts
@@ -2,6 +2,7 @@ import {
AllPublishOptions,
BaseS3Options,
BintrayOptions,
+ BitbucketOptions,
CustomPublishOptions,
GenericServerOptions,
getS3LikeProviderBaseUrl,
@@ -12,6 +13,7 @@ import {
} from "builder-util-runtime"
import { AppUpdater } from "./AppUpdater"
import { BintrayProvider } from "./providers/BintrayProvider"
+import { BitbucketProvider } from "./providers/BitbucketProvider"
import { GenericProvider } from "./providers/GenericProvider"
import { GitHubProvider } from "./providers/GitHubProvider"
import { KeygenProvider } from "./providers/KeygenProvider"
@@ -40,6 +42,9 @@ export function createClient(data: PublishConfiguration | AllPublishOptions, upd
}
}
+ case "bitbucket":
+ return new BitbucketProvider(data as BitbucketOptions, updater, runtimeOptions)
+
case "keygen":
return new KeygenProvider(data as KeygenOptions, updater, runtimeOptions)
diff --git a/packages/electron-updater/src/providers/BitbucketProvider.ts b/packages/electron-updater/src/providers/BitbucketProvider.ts
new file mode 100644
index 00000000000..82521282601
--- /dev/null
+++ b/packages/electron-updater/src/providers/BitbucketProvider.ts
@@ -0,0 +1,43 @@
+import { CancellationToken, BitbucketOptions, newError, UpdateInfo } from "builder-util-runtime"
+import { AppUpdater } from "../AppUpdater"
+import { ResolvedUpdateFileInfo } from "../main"
+import { getChannelFilename, newBaseUrl, newUrlFromBase } from "../util"
+import { parseUpdateInfo, Provider, ProviderRuntimeOptions, resolveFiles } from "./Provider"
+
+export class BitbucketProvider extends Provider {
+ private readonly baseUrl: URL
+
+ constructor(private readonly configuration: BitbucketOptions, private readonly updater: AppUpdater, runtimeOptions: ProviderRuntimeOptions) {
+ super({
+ ...runtimeOptions,
+ isUseMultipleRangeRequest: false,
+ })
+ const { owner, slug } = configuration
+ this.baseUrl = newBaseUrl(`https://api.bitbucket.org/2.0/repositories/${owner}/${slug}/downloads`)
+ }
+
+ private get channel(): string {
+ return this.updater.channel || this.configuration.channel || "latest"
+ }
+
+ async getLatestVersion(): Promise {
+ const cancellationToken = new CancellationToken()
+ const channelFile = getChannelFilename(this.getCustomChannelName(this.channel))
+ const channelUrl = newUrlFromBase(channelFile, this.baseUrl, this.updater.isAddNoCacheQuery)
+ try {
+ const updateInfo = await this.httpRequest(channelUrl, undefined, cancellationToken)
+ return parseUpdateInfo(updateInfo, channelFile, channelUrl)
+ } catch (e) {
+ throw newError(`Unable to find latest version on ${this.toString()}, please ensure release exists: ${e.stack || e.message}`, "ERR_UPDATER_LATEST_VERSION_NOT_FOUND")
+ }
+ }
+
+ resolveFiles(updateInfo: UpdateInfo): Array {
+ return resolveFiles(updateInfo, this.baseUrl)
+ }
+
+ toString() {
+ const { owner, slug } = this.configuration
+ return `Bitbucket (owner: ${owner}, slug: ${slug}, channel: ${this.channel})`
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7ee758cffbc..2bfefeaba63 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -103,6 +103,7 @@ importers:
electron-builder-squirrel-windows: workspace:*
electron-osx-sign: ^0.5.0
electron-publish: workspace:*
+ form-data: ^4.0.0
fs-extra: ^10.0.0
hosted-git-info: ^4.0.2
is-ci: ^3.0.0
@@ -128,6 +129,7 @@ importers:
ejs: 3.1.6
electron-osx-sign: 0.5.0
electron-publish: link:../electron-publish
+ form-data: 4.0.0
fs-extra: 10.0.0
hosted-git-info: 4.0.2
is-ci: 3.0.0
@@ -5610,6 +5612,15 @@ packages:
combined-stream: 1.0.8
mime-types: 2.1.32
+ /form-data/4.0.0:
+ resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ engines: {node: '>= 6'}
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.32
+ dev: false
+
/fragment-cache/0.2.1:
resolution: {integrity: sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=}
engines: {node: '>=0.10.0'}
diff --git a/scripts/jsdoc2md2html.js b/scripts/jsdoc2md2html.js
index d49679545bd..32fbcaf2168 100644
--- a/scripts/jsdoc2md2html.js
+++ b/scripts/jsdoc2md2html.js
@@ -215,8 +215,9 @@ async function render2(files, jsdoc2MdOptions) {
"SnapStoreOptions": "",
"SpacesOptions": "",
"KeygenOptions": "",
+ "BitbucketOptions": "",
+ "S3Options": ""
}),
- new Page("generated/s3-options.md", "S3Options"),
new Page("generated/appimage-options.md", "AppImageOptions"),
new Page("generated/DebOptions.md", "DebOptions"),
diff --git a/test/src/ArtifactPublisherTest.ts b/test/src/ArtifactPublisherTest.ts
index f94f0e37713..a99512708f5 100644
--- a/test/src/ArtifactPublisherTest.ts
+++ b/test/src/ArtifactPublisherTest.ts
@@ -1,5 +1,5 @@
import { Arch } from "builder-util"
-import { CancellationToken, HttpError, KeygenOptions, S3Options, SpacesOptions } from "builder-util-runtime"
+import { BitbucketOptions, CancellationToken, HttpError, KeygenOptions, S3Options, SpacesOptions } from "builder-util-runtime"
import { PublishContext } from "electron-publish"
import { GitHubPublisher } from "electron-publish/out/gitHubPublisher"
import { isCI as isCi } from "ci-info"
@@ -7,6 +7,7 @@ import * as path from "path"
import { KeygenPublisher } from "app-builder-lib/out/publish/KeygenPublisher"
import { Platform } from "app-builder-lib"
import { createPublisher } from "app-builder-lib/out/publish/PublishManager"
+import { BitbucketPublisher } from "app-builder-lib/out/publish/BitbucketPublisher"
if (isCi && process.platform === "win32") {
fit("Skip ArtifactPublisherTest suite on Windows CI", () => {
@@ -140,3 +141,13 @@ test.ifEnv(process.env.KEYGEN_TOKEN)("Keygen upload", async () => {
const releaseId = await publisher.upload({ file: iconPath, arch: Arch.x64 })
await publisher.deleteRelease(releaseId)
})
+
+test.ifEnv(process.env.BITBUCKET_TOKEN)("Bitbucket upload", async () => {
+ const publisher = new BitbucketPublisher(publishContext, {
+ provider: "bitbucket",
+ owner: "mike-m",
+ slug: "electron-builder-test",
+ } as BitbucketOptions)
+ const filename = await publisher.upload({ file: iconPath, arch: Arch.x64 })
+ await publisher.deleteRelease(filename)
+})
diff --git a/test/src/helpers/updaterTestUtil.ts b/test/src/helpers/updaterTestUtil.ts
index 6d38dcaf4c2..b0a002ec029 100644
--- a/test/src/helpers/updaterTestUtil.ts
+++ b/test/src/helpers/updaterTestUtil.ts
@@ -1,5 +1,5 @@
import { serializeToYaml, TmpDir } from "builder-util"
-import { BintrayOptions, GenericServerOptions, GithubOptions, S3Options, SpacesOptions, DownloadOptions, KeygenOptions } from "builder-util-runtime"
+import { DownloadOptions, AllPublishOptions } from "builder-util-runtime"
import { AppUpdater, NoOpLogger } from "electron-updater"
import { MacUpdater } from "electron-updater/out/MacUpdater"
import { outputFile, writeFile } from "fs-extra"
@@ -12,19 +12,19 @@ import { NodeHttpExecutor } from "builder-util/out/nodeHttpExecutor"
const tmpDir = new TmpDir("updater-test-util")
-export async function createTestAppAdapter(version: string = "0.0.1") {
+export async function createTestAppAdapter(version = "0.0.1") {
return new TestAppAdapter(version, await tmpDir.getTempDir())
}
-export async function createNsisUpdater(version: string = "0.0.1") {
+export async function createNsisUpdater(version = "0.0.1") {
const testAppAdapter = await createTestAppAdapter(version)
const result = new NsisUpdater(null, testAppAdapter)
- await tuneTestUpdater(result)
+ tuneTestUpdater(result)
return result
}
// to reduce difference in test mode, setFeedURL is not used to set (NsisUpdater also read configOnDisk to load original publisherName)
-export async function writeUpdateConfig(data: T): Promise {
+export async function writeUpdateConfig(data: T): Promise {
const updateConfigPath = path.join(await tmpDir.getTempDir({ prefix: "test-update-config" }), "app-update.yml")
await outputFile(updateConfigPath, serializeToYaml(data))
return updateConfigPath
@@ -49,7 +49,7 @@ export async function validateDownload(updater: AppUpdater, expectDownloadPromis
if (updater instanceof MacUpdater) {
expect(downloadResult).toEqual([])
} else {
- await assertThat(path.join(downloadResult!![0])).isFile()
+ await assertThat(path.join(downloadResult![0])).isFile()
}
} else {
// noinspection JSIgnoredPromiseFromCall
@@ -71,7 +71,7 @@ export class TestNodeHttpExecutor extends NodeHttpExecutor {
export const httpExecutor: TestNodeHttpExecutor = new TestNodeHttpExecutor()
-export async function tuneTestUpdater(updater: AppUpdater, options?: TestOnlyUpdaterOptions) {
+export function tuneTestUpdater(updater: AppUpdater, options?: TestOnlyUpdaterOptions) {
;(updater as any).httpExecutor = httpExecutor
;(updater as any)._testOnlyOptions = {
platform: "win32",
diff --git a/test/src/updater/nsisUpdaterTest.ts b/test/src/updater/nsisUpdaterTest.ts
index eceb48664e0..5dcb09eb814 100644
--- a/test/src/updater/nsisUpdaterTest.ts
+++ b/test/src/updater/nsisUpdaterTest.ts
@@ -1,4 +1,5 @@
-import { GenericServerOptions, GithubOptions, KeygenOptions, S3Options, SpacesOptions } from "builder-util-runtime"
+import { BitbucketPublisher } from "app-builder-lib/out/publish/BitbucketPublisher"
+import { BitbucketOptions, GenericServerOptions, GithubOptions, KeygenOptions, S3Options, SpacesOptions } from "builder-util-runtime"
import { UpdateCheckResult } from "electron-updater"
import { outputFile } from "fs-extra"
import { tmpdir } from "os"
@@ -57,6 +58,18 @@ test.ifEnv(process.env.KEYGEN_TOKEN)("file url keygen", async () => {
await validateDownload(updater)
})
+test.ifEnv(process.env.BITBUCKET_TOKEN)("file url bitbucket", async () => {
+ const updater = await createNsisUpdater()
+ const options: BitbucketOptions = {
+ provider: "bitbucket",
+ owner: "mike-m",
+ slug: "electron-builder-test",
+ }
+ updater.addAuthHeader(BitbucketPublisher.convertAppPassword(options.owner, process.env.BITBUCKET_TOKEN!))
+ updater.updateConfigPath = await writeUpdateConfig(options)
+ await validateDownload(updater)
+})
+
test.skip("DigitalOcean Spaces", async () => {
const updater = await createNsisUpdater()
updater.updateConfigPath = await writeUpdateConfig({