Skip to content

Commit d1cf92c

Browse files
committed
feat: upgrade keygen integration to v1.1
1 parent 03cc9b9 commit d1cf92c

File tree

5 files changed

+188
-46
lines changed

5 files changed

+188
-46
lines changed

.changeset/seven-frogs-attend.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"app-builder-lib": minor
3+
"electron-updater": minor
4+
---
5+
6+
Upgrade Keygen publisher/updater integration to API version v1.1.

packages/app-builder-lib/src/publish/KeygenPublisher.ts

Lines changed: 177 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,77 @@ import { KeygenOptions } from "builder-util-runtime/out/publishOptions"
66
import { configureRequestOptions, HttpExecutor, parseJson } from "builder-util-runtime"
77
import { getCompleteExtname } from "../util/filename"
88

9+
type RecursivePartial<T> = {
10+
[P in keyof T]?: RecursivePartial<T[P]>
11+
}
12+
13+
export interface KeygenError {
14+
title: string
15+
detail: string
16+
code: string
17+
}
18+
19+
export interface KeygenRelease {
20+
id: string
21+
type: "releases"
22+
attributes: {
23+
name: string | null
24+
description: string | null
25+
channel: "stable" | "rc" | "beta" | "alpha" | "dev"
26+
status: "DRAFT" | "PUBLISHED" | "YANKED"
27+
tag: string
28+
version: string
29+
semver: {
30+
major: number
31+
minor: number
32+
patch: number
33+
prerelease: string | null
34+
build: string | null
35+
}
36+
metadata: { [s: string]: any }
37+
created: string
38+
updated: string
39+
yanked: string | null
40+
}
41+
relationships: {
42+
account: {
43+
data: { type: "accounts"; id: string }
44+
}
45+
product: {
46+
data: { type: "products"; id: string }
47+
}
48+
}
49+
}
50+
51+
export interface KeygenArtifact {
52+
id: string
53+
type: "artifacts"
54+
attributes: {
55+
filename: string
56+
filetype: string | null
57+
filesize: number | null
58+
platform: string | null
59+
arch: string | null
60+
signature: string | null
61+
checksum: string | null
62+
status: "WAITING" | "UPLOADED" | "FAILED" | "YANKED"
63+
metadata: { [s: string]: any }
64+
created: string
65+
updated: string
66+
}
67+
relationships: {
68+
account: {
69+
data: { type: "accounts"; id: string }
70+
}
71+
release: {
72+
data: { type: "releases"; id: string }
73+
}
74+
}
75+
links: {
76+
redirect: string
77+
}
78+
}
79+
980
export class KeygenPublisher extends HttpPublisher {
1081
readonly providerName = "keygen"
1182
readonly hostname = "api.keygen.sh"
@@ -26,7 +97,7 @@ export class KeygenPublisher extends HttpPublisher {
2697
this.info = info
2798
this.auth = `Bearer ${token.trim()}`
2899
this.version = version
29-
this.basePath = `/v1/accounts/${this.info.account}/releases`
100+
this.basePath = `/v1/accounts/${this.info.account}`
30101
}
31102

32103
protected doUpload(
@@ -36,78 +107,143 @@ export class KeygenPublisher extends HttpPublisher {
36107
requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void,
37108
// eslint-disable-next-line @typescript-eslint/no-unused-vars
38109
_file: string
39-
): Promise<any> {
110+
): Promise<string> {
40111
return HttpExecutor.retryOnServerError(async () => {
41-
const { data, errors } = await this.upsertRelease(fileName, dataLength)
112+
const { data, errors } = await this.getOrCreateRelease()
42113
if (errors) {
43-
throw new Error(`Keygen - Upserting release returned errors: ${JSON.stringify(errors)}`)
114+
throw new Error(`Keygen - Creating release returned errors: ${JSON.stringify(errors)}`)
44115
}
45-
const releaseId = data?.id
46-
if (!releaseId) {
47-
log.warn({ file: fileName, reason: "UUID doesn't exist and was not created" }, "upserting release failed")
48-
throw new Error(`Keygen - Upserting release returned no UUID: ${JSON.stringify(data)}`)
49-
}
50-
await this.uploadArtifact(releaseId, dataLength, requestProcessor)
51-
return releaseId
116+
117+
await this.uploadArtifact(data!.id, fileName, dataLength, requestProcessor)
118+
119+
return data!.id
52120
})
53121
}
54122

55-
private async uploadArtifact(releaseId: any, dataLength: number, requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void) {
123+
private async uploadArtifact(
124+
releaseId: any,
125+
fileName: string,
126+
dataLength: number,
127+
requestProcessor: (request: ClientRequest, reject: (error: Error) => void) => void
128+
): Promise<void> {
129+
const { data, errors } = await this.createArtifact(releaseId, fileName, dataLength)
130+
if (errors) {
131+
throw new Error(`Keygen - Creating artifact returned errors: ${JSON.stringify(errors)}`)
132+
}
133+
134+
// Follow the redirect and upload directly to S3-equivalent storage provider
135+
const url = new URL(data!.links.redirect)
136+
const upload: RequestOptions = {
137+
hostname: url.hostname,
138+
path: url.pathname + url.search,
139+
headers: {
140+
"Content-Length": dataLength,
141+
},
142+
}
143+
144+
await httpExecutor.doApiRequest(configureRequestOptions(upload, null, "PUT"), this.context.cancellationToken, requestProcessor)
145+
}
146+
147+
private async createArtifact(releaseId: any, fileName: string, dataLength: number): Promise<{ data?: KeygenArtifact; errors?: KeygenError[] }> {
56148
const upload: RequestOptions = {
57149
hostname: this.hostname,
58-
path: `${this.basePath}/${releaseId}/artifact`,
150+
path: `${this.basePath}/artifacts`,
59151
headers: {
152+
"Content-Type": "application/vnd.api+json",
60153
Accept: "application/vnd.api+json",
61-
"Content-Length": dataLength,
62-
"Keygen-Version": "1.0",
154+
"Keygen-Version": "1.1",
155+
Prefer: "no-redirect",
156+
},
157+
}
158+
159+
const data: RecursivePartial<KeygenArtifact> = {
160+
type: "artifacts",
161+
attributes: {
162+
filename: fileName,
163+
filetype: getCompleteExtname(fileName),
164+
filesize: dataLength,
165+
platform: this.info.platform,
166+
},
167+
relationships: {
168+
release: {
169+
data: {
170+
type: "releases",
171+
id: releaseId,
172+
},
173+
},
63174
},
64175
}
65-
await httpExecutor.doApiRequest(configureRequestOptions(upload, this.auth, "PUT"), this.context.cancellationToken, requestProcessor)
176+
177+
log.debug({ data: JSON.stringify(data) }, "Keygen create artifact")
178+
179+
return parseJson(httpExecutor.request(configureRequestOptions(upload, this.auth, "POST"), this.context.cancellationToken, { data }))
66180
}
67181

68-
private async upsertRelease(fileName: string, dataLength: number): Promise<{ data: any; errors: any }> {
182+
private async getOrCreateRelease(): Promise<{ data?: KeygenRelease; errors?: KeygenError[] }> {
183+
try {
184+
return await this.getRelease()
185+
} catch (e) {
186+
if (e.statusCode === 404) {
187+
return this.createRelease()
188+
}
189+
190+
throw e
191+
}
192+
}
193+
194+
private async getRelease(): Promise<{ data?: KeygenRelease; errors?: KeygenError[] }> {
69195
const req: RequestOptions = {
70196
hostname: this.hostname,
71-
method: "PUT",
72-
path: this.basePath,
197+
path: `${this.basePath}/releases/${this.version}`,
198+
headers: {
199+
Accept: "application/vnd.api+json",
200+
"Keygen-Version": "1.1",
201+
},
202+
}
203+
204+
return parseJson(httpExecutor.request(configureRequestOptions(req, this.auth, "GET"), this.context.cancellationToken, null))
205+
}
206+
207+
private async createRelease(): Promise<{ data?: KeygenRelease; errors?: KeygenError[] }> {
208+
const req: RequestOptions = {
209+
hostname: this.hostname,
210+
path: `${this.basePath}/releases`,
73211
headers: {
74212
"Content-Type": "application/vnd.api+json",
75213
Accept: "application/vnd.api+json",
76-
"Keygen-Version": "1.0",
214+
"Keygen-Version": "1.1",
77215
},
78216
}
79-
const data = {
80-
data: {
81-
type: "release",
82-
attributes: {
83-
filename: fileName,
84-
filetype: getCompleteExtname(fileName),
85-
filesize: dataLength,
86-
version: this.version,
87-
platform: this.info.platform,
88-
channel: this.info.channel || "stable",
89-
},
90-
relationships: {
91-
product: {
92-
data: {
93-
type: "product",
94-
id: this.info.product,
95-
},
217+
218+
const data: RecursivePartial<KeygenRelease> = {
219+
type: "releases",
220+
attributes: {
221+
version: this.version,
222+
channel: this.info.channel || "stable",
223+
status: "PUBLISHED",
224+
},
225+
relationships: {
226+
product: {
227+
data: {
228+
type: "products",
229+
id: this.info.product,
96230
},
97231
},
98232
},
99233
}
100-
log.debug({ data: JSON.stringify(data) }, "Keygen upsert release")
101-
return parseJson(httpExecutor.request(configureRequestOptions(req, this.auth, "PUT"), this.context.cancellationToken, data))
234+
235+
log.debug({ data: JSON.stringify(data) }, "Keygen create release")
236+
237+
return parseJson(httpExecutor.request(configureRequestOptions(req, this.auth, "POST"), this.context.cancellationToken, { data }))
102238
}
103239

104240
async deleteRelease(releaseId: string): Promise<void> {
105241
const req: RequestOptions = {
106242
hostname: this.hostname,
107-
path: `${this.basePath}/${releaseId}`,
243+
path: `${this.basePath}/releases/${releaseId}`,
108244
headers: {
109245
Accept: "application/vnd.api+json",
110-
"Keygen-Version": "1.0",
246+
"Keygen-Version": "1.1",
111247
},
112248
}
113249
await httpExecutor.request(configureRequestOptions(req, this.auth, "DELETE"), this.context.cancellationToken)

packages/electron-updater/src/providers/KeygenProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class KeygenProvider extends Provider<UpdateInfo> {
2828
channelUrl,
2929
{
3030
Accept: "application/vnd.api+json",
31-
"Keygen-Version": "1.0",
31+
"Keygen-Version": "1.1",
3232
},
3333
cancellationToken
3434
)

test/src/ArtifactPublisherTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,8 @@ test.ifEnv(process.env.KEYGEN_TOKEN)("Keygen upload", async () => {
132132
{
133133
provider: "keygen",
134134
// electron-builder-test
135-
product: "43981278-96e7-47de-b8c2-98d59987206b",
136-
account: "cdecda36-3ef0-483e-ad88-97e7970f3149",
135+
product: process.env.KEYGEN_PRODUCT || "43981278-96e7-47de-b8c2-98d59987206b",
136+
account: process.env.KEYGEN_ACCOUNT || "cdecda36-3ef0-483e-ad88-97e7970f3149",
137137
platform: Platform.MAC.name,
138138
} as KeygenOptions,
139139
versionNumber()

test/src/updater/nsisUpdaterTest.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ test.ifEnv(process.env.KEYGEN_TOKEN)("file url keygen", async () => {
5555
updater.addAuthHeader(`Bearer ${process.env.KEYGEN_TOKEN}`)
5656
updater.updateConfigPath = await writeUpdateConfig<KeygenOptions>({
5757
provider: "keygen",
58-
product: "43981278-96e7-47de-b8c2-98d59987206b",
59-
account: "cdecda36-3ef0-483e-ad88-97e7970f3149",
58+
product: process.env.KEYGEN_PRODUCT || "43981278-96e7-47de-b8c2-98d59987206b",
59+
account: process.env.KEYGEN_ACCOUNT || "cdecda36-3ef0-483e-ad88-97e7970f3149",
6060
})
6161
await validateDownload(updater)
6262
})

0 commit comments

Comments
 (0)