1- import { got } from 'got '
1+ import { Client , interceptors , fetch } from 'undici '
22import { valid , gt , lt , eq } from 'semver'
33import chalk from 'chalk'
44
5- import { connect , getRestClient } from '@existdb/node-exist'
5+ import { getXmlRpcClient , getRestClient } from '@existdb/node-exist'
66
77import { isDBAdmin , getUserInfo } from '../../../utility/connection.js'
88import {
@@ -12,9 +12,24 @@ import {
1212} from '../../../utility/package.js'
1313import { logFailure , logSuccess , logSkipped } from '../../../utility/message.js'
1414
15- async function getRelease ( api , owner , repo , release , assetFilter , verbose ) {
15+ /**
16+ * @typedef { import("undici").Dispatcher.ComposedDispatcher } Dispatcher.ComposedDispatcher
17+ */
18+
19+ /**
20+ * get the release information from Github API
21+ * @param {Dispatcher.ComposedDispatcher } api
22+ * @param {String } address the api address
23+ * @param {String } owner the owner of the repo
24+ * @param {String } repo the name of the repo
25+ * @param {String } [release] the specific release naem (optional)
26+ * @param {String } assetFilter the pattern to find the XAR
27+ * @param {boolean } verbose display more information
28+ * @returns {Promise<{ xarName: string, packageContents: string, releaseName: string, tagName:string }> } release info
29+ */
30+ async function getRelease ( api , origin , owner , repo , release , assetFilter , verbose ) {
1631 const tag = release === 'latest' ? release : 'tags/' + release
17- const path = `repos/${ owner } /${ repo } /releases/${ tag } `
32+ const path = `/ repos/${ owner } /${ repo } /releases/${ tag } `
1833 /**
1934 * @type {unknown[] }
2035 */
@@ -28,14 +43,15 @@ async function getRelease (api, owner, repo, release, assetFilter, verbose) {
2843 */
2944 let tagName
3045 try {
31- const result = await got . get ( path , { prefixUrl : api } ) . json ( )
46+ const { body } = await api . request ( { method : 'GET' , path } )
47+ const result = await body . json ( )
3248 // The name is not always filled in. Fall back to the tag_name if it is absent
3349 releaseName = result . name || result . tag_name
3450 assets = result . assets
3551 tagName = result . tag_name
3652 } catch ( e ) {
3753 throw Error (
38- `Could not get release from: ${ e . options . url } ${ e . response . statusCode } : ${ e . response . statusMessage } `
54+ `Could not get release from: ${ origin } ${ path } ${ e . statusCode } : ${ e . body . message } `
3955 )
4056 }
4157 const filteredAssets = assets . filter ( assetFilter )
@@ -59,6 +75,15 @@ async function getRelease (api, owner, repo, release, assetFilter, verbose) {
5975 }
6076}
6177
78+ /**
79+ * Install a package after it was uploaded
80+ * @param {import('@existdb/node-exist').NodeExistXmlRpcClient } db the XML-RPC client connected to an exist-db
81+ * @param {Function } upload a function uploading the XAR package
82+ * @param {string } xarName the name of the XAR package
83+ * @param {Readable } contents the ReadableStream that will be put to the database
84+ * @param {string } registry the URL to the registry that will be used ot resolve dependencies
85+ * @returns {Promise<{ success: boolean, error?: Error }> } the result of the installation
86+ */
6287async function install ( db , upload , xarName , contents , registry ) {
6388 const xarDisplay = chalk . dim ( xarName )
6489 const uploadResult = await upload ( contents , xarName )
@@ -128,6 +153,14 @@ export const builder = (yargs) => {
128153 . options ( options )
129154}
130155
156+ const requestHeaderInterceptor = ( baseheaders ) => dispatch => {
157+ return ( opts , handler ) => {
158+ const { headers } = opts
159+ opts . headers = { ...headers , ...baseheaders }
160+ return dispatch ( opts , handler )
161+ }
162+ }
163+
131164export async function handler ( argv ) {
132165 if ( argv . help ) {
133166 return 0
@@ -148,7 +181,7 @@ export async function handler (argv) {
148181 } = argv
149182
150183 const repo = argv . repo && argv . repo !== '' ? argv . repo : abbrev
151- const db = connect ( connectionOptions )
184+ const db = getXmlRpcClient ( connectionOptions )
152185
153186 // check permissions (and therefore implicitly the connection)
154187 const user = await getUserInfo ( db )
@@ -158,7 +191,7 @@ export async function handler (argv) {
158191 )
159192 }
160193
161- const restClient = await getRestClient ( connectionOptions )
194+ const restClient = getRestClient ( connectionOptions )
162195 // check rest connection
163196 await restClient . get ( 'db' )
164197 const upload = putPackage . bind ( null , db , restClient )
@@ -170,11 +203,16 @@ export async function handler (argv) {
170203 }
171204
172205 const installedVersion = ( await getInstalledPackageMeta ( db , abbrev ) ) . version
206+ const apiClient = new Client ( api ) . compose ( [
207+ requestHeaderInterceptor ( { 'User-Agent' : 'xst/undici.Client' } ) ,
208+ interceptors . responseError ( { throwOnError : true } )
209+ ] )
173210
174211 if ( verbose ) {
175212 console . log ( `Preparing to install ${ owner } /${ repo } at version ${ release } ` )
176213 }
177214 const { xarName, packageContents, releaseName, tagName } = await getRelease (
215+ apiClient ,
178216 api ,
179217 owner ,
180218 repo ,
@@ -219,8 +257,10 @@ export async function handler (argv) {
219257 }
220258
221259 try {
222- const contentStream = got . stream . get ( packageContents )
223- const result = await install ( db , upload , xarName , contentStream , registry )
260+ // using fetch here as the packageContents could be anywhere on the internet
261+ // and the contents might be gzipped, so we benefit from automatic decompression
262+ const { body } = await fetch ( packageContents , { method : 'GET' } )
263+ const result = await install ( db , upload , xarName , body , registry )
224264 let action
225265 if ( isDowngrade ) {
226266 action = `${ chalk . yellow ( 'downgraded' ) } to`
0 commit comments