@@ -6,6 +6,77 @@ import { KeygenOptions } from "builder-util-runtime/out/publishOptions"
66import { configureRequestOptions , HttpExecutor , parseJson } from "builder-util-runtime"
77import { 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+
980export 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 )
0 commit comments