mirror of https://github.com/actions/toolkit
Merge branch 'main' into robherley/artifact-digest
commit
eb7ed88d77
|
@ -5,7 +5,7 @@ on:
|
||||||
inputs:
|
inputs:
|
||||||
package:
|
package:
|
||||||
required: true
|
required: true
|
||||||
description: 'core, artifact, cache, exec, github, glob, io, tool-cache'
|
description: 'core, artifact, cache, exec, github, glob, http-client, io, tool-cache'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|
|
@ -46,6 +46,15 @@ $ npm install @actions/glob
|
||||||
```
|
```
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
:phone: [@actions/http-client](packages/http-client)
|
||||||
|
|
||||||
|
A lightweight HTTP client optimized for building actions. Read more [here](packages/http-client)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install @actions/http-client
|
||||||
|
```
|
||||||
|
<br/>
|
||||||
|
|
||||||
:pencil2: [@actions/io](packages/io)
|
:pencil2: [@actions/io](packages/io)
|
||||||
|
|
||||||
Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io)
|
Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io)
|
||||||
|
|
|
@ -77,4 +77,12 @@
|
||||||
|
|
||||||
### 1.0.0
|
### 1.0.0
|
||||||
|
|
||||||
- Update `lockfileVersion` to `v2` in `package-lock.json [#1009](https://github.com/actions/toolkit/pull/1009)
|
- Update `lockfileVersion` to `v2` in `package-lock.json` [#1009](https://github.com/actions/toolkit/pull/1009)
|
||||||
|
|
||||||
|
### 1.0.1
|
||||||
|
|
||||||
|
- Update to v2.0.0 of `@actions/http-client`
|
||||||
|
|
||||||
|
### 1.0.2
|
||||||
|
|
||||||
|
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
|
@ -3,7 +3,6 @@ import * as net from 'net'
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import * as configVariables from '../src/internal/config-variables'
|
import * as configVariables from '../src/internal/config-variables'
|
||||||
import {retry} from '../src/internal/requestUtils'
|
import {retry} from '../src/internal/requestUtils'
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
|
||||||
import {HttpClientResponse} from '@actions/http-client'
|
import {HttpClientResponse} from '@actions/http-client'
|
||||||
|
|
||||||
jest.mock('../src/internal/config-variables')
|
jest.mock('../src/internal/config-variables')
|
||||||
|
@ -42,7 +41,7 @@ async function testRetry(
|
||||||
|
|
||||||
async function handleResponse(
|
async function handleResponse(
|
||||||
testResponseCode: number | undefined
|
testResponseCode: number | undefined
|
||||||
): Promise<IHttpClientResponse> {
|
): Promise<HttpClientResponse> {
|
||||||
if (!testResponseCode) {
|
if (!testResponseCode) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Test incorrectly set up. reverse.pop() was called too many times so not enough test response codes were supplied'
|
'Test incorrectly set up. reverse.pop() was called too many times so not enough test response codes were supplied'
|
||||||
|
@ -72,7 +71,7 @@ async function emptyMockReadBody(): Promise<string> {
|
||||||
|
|
||||||
async function setupSingleMockResponse(
|
async function setupSingleMockResponse(
|
||||||
statusCode: number
|
statusCode: number
|
||||||
): Promise<IHttpClientResponse> {
|
): Promise<HttpClientResponse> {
|
||||||
const mockMessage = new http.IncomingMessage(new net.Socket())
|
const mockMessage = new http.IncomingMessage(new net.Socket())
|
||||||
const mockReadBody = emptyMockReadBody
|
const mockReadBody = emptyMockReadBody
|
||||||
mockMessage.statusCode = statusCode
|
mockMessage.statusCode = statusCode
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "0.6.1",
|
"version": "1.0.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "0.6.1",
|
"version": "1.0.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/http-client": "^1.0.11",
|
"@actions/http-client": "^2.0.1",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"tmp-promise": "^3.0.2"
|
"tmp-promise": "^3.0.2"
|
||||||
},
|
},
|
||||||
|
@ -20,14 +20,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^1.0.11"
|
"@actions/http-client": "^1.0.11"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/core/node_modules/@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "1.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
@ -35,6 +35,14 @@
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@actions/http-client": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "^0.0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/tmp": {
|
"node_modules/@types/tmp": {
|
||||||
"version": "0.2.3",
|
"version": "0.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz",
|
||||||
|
@ -187,19 +195,29 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.6.0",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/http-client": "^1.0.11"
|
"@actions/http-client": "^1.0.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"requires": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/tmp": {
|
"@types/tmp": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/artifact",
|
"name": "@actions/artifact",
|
||||||
"version": "1.0.0",
|
"version": "1.0.2",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions artifact lib",
|
"description": "Actions artifact lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/http-client": "^1.0.11",
|
"@actions/http-client": "^2.0.1",
|
||||||
"tmp": "^0.2.1",
|
"tmp": "^0.2.1",
|
||||||
"tmp-promise": "^3.0.2"
|
"tmp-promise": "^3.0.2"
|
||||||
},
|
},
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {URL} from 'url'
|
||||||
import {StatusReporter} from './status-reporter'
|
import {StatusReporter} from './status-reporter'
|
||||||
import {performance} from 'perf_hooks'
|
import {performance} from 'perf_hooks'
|
||||||
import {ListArtifactsResponse, QueryArtifactResponse} from './contracts'
|
import {ListArtifactsResponse, QueryArtifactResponse} from './contracts'
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
import {HttpClientResponse} from '@actions/http-client'
|
||||||
import {HttpManager} from './http-manager'
|
import {HttpManager} from './http-manager'
|
||||||
import {DownloadItem} from './download-specification'
|
import {DownloadItem} from './download-specification'
|
||||||
import {getDownloadFileConcurrency, getRetryLimit} from './config-variables'
|
import {getDownloadFileConcurrency, getRetryLimit} from './config-variables'
|
||||||
|
@ -152,7 +152,7 @@ export class DownloadHttpClient {
|
||||||
const headers = getDownloadHeaders('application/json', true, true)
|
const headers = getDownloadHeaders('application/json', true, true)
|
||||||
|
|
||||||
// a single GET request is used to download a file
|
// a single GET request is used to download a file
|
||||||
const makeDownloadRequest = async (): Promise<IHttpClientResponse> => {
|
const makeDownloadRequest = async (): Promise<HttpClientResponse> => {
|
||||||
const client = this.downloadHttpManager.getClient(httpClientIndex)
|
const client = this.downloadHttpManager.getClient(httpClientIndex)
|
||||||
return await client.get(artifactLocation, headers)
|
return await client.get(artifactLocation, headers)
|
||||||
}
|
}
|
||||||
|
@ -225,7 +225,7 @@ export class DownloadHttpClient {
|
||||||
|
|
||||||
// keep trying to download a file until a retry limit has been reached
|
// keep trying to download a file until a retry limit has been reached
|
||||||
while (retryCount <= retryLimit) {
|
while (retryCount <= retryLimit) {
|
||||||
let response: IHttpClientResponse
|
let response: HttpClientResponse
|
||||||
try {
|
try {
|
||||||
response = await makeDownloadRequest()
|
response = await makeDownloadRequest()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
@ -295,7 +295,7 @@ export class DownloadHttpClient {
|
||||||
* @param isGzip a boolean denoting if the content is compressed using gzip and if we need to decode it
|
* @param isGzip a boolean denoting if the content is compressed using gzip and if we need to decode it
|
||||||
*/
|
*/
|
||||||
async pipeResponseToFile(
|
async pipeResponseToFile(
|
||||||
response: IHttpClientResponse,
|
response: HttpClientResponse,
|
||||||
destinationStream: fs.WriteStream,
|
destinationStream: fs.WriteStream,
|
||||||
isGzip: boolean
|
isGzip: boolean
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {HttpClient} from '@actions/http-client/index'
|
import {HttpClient} from '@actions/http-client'
|
||||||
import {createHttpClient} from './utils'
|
import {createHttpClient} from './utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
import {HttpClientResponse} from '@actions/http-client'
|
||||||
import {
|
import {
|
||||||
isRetryableStatusCode,
|
isRetryableStatusCode,
|
||||||
isSuccessStatusCode,
|
isSuccessStatusCode,
|
||||||
|
@ -11,11 +11,11 @@ import {getRetryLimit} from './config-variables'
|
||||||
|
|
||||||
export async function retry(
|
export async function retry(
|
||||||
name: string,
|
name: string,
|
||||||
operation: () => Promise<IHttpClientResponse>,
|
operation: () => Promise<HttpClientResponse>,
|
||||||
customErrorMessages: Map<number, string>,
|
customErrorMessages: Map<number, string>,
|
||||||
maxAttempts: number
|
maxAttempts: number
|
||||||
): Promise<IHttpClientResponse> {
|
): Promise<HttpClientResponse> {
|
||||||
let response: IHttpClientResponse | undefined = undefined
|
let response: HttpClientResponse | undefined = undefined
|
||||||
let statusCode: number | undefined = undefined
|
let statusCode: number | undefined = undefined
|
||||||
let isRetryable = false
|
let isRetryable = false
|
||||||
let errorMessage = ''
|
let errorMessage = ''
|
||||||
|
@ -71,9 +71,9 @@ export async function retry(
|
||||||
|
|
||||||
export async function retryHttpClientRequest(
|
export async function retryHttpClientRequest(
|
||||||
name: string,
|
name: string,
|
||||||
method: () => Promise<IHttpClientResponse>,
|
method: () => Promise<HttpClientResponse>,
|
||||||
customErrorMessages: Map<number, string> = new Map(),
|
customErrorMessages: Map<number, string> = new Map(),
|
||||||
maxAttempts = getRetryLimit()
|
maxAttempts = getRetryLimit()
|
||||||
): Promise<IHttpClientResponse> {
|
): Promise<HttpClientResponse> {
|
||||||
return await retry(name, method, customErrorMessages, maxAttempts)
|
return await retry(name, method, customErrorMessages, maxAttempts)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,8 +32,7 @@ import {promisify} from 'util'
|
||||||
import {URL} from 'url'
|
import {URL} from 'url'
|
||||||
import {performance} from 'perf_hooks'
|
import {performance} from 'perf_hooks'
|
||||||
import {StatusReporter} from './status-reporter'
|
import {StatusReporter} from './status-reporter'
|
||||||
import {HttpCodes} from '@actions/http-client'
|
import {HttpCodes, HttpClientResponse} from '@actions/http-client'
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
|
||||||
import {HttpManager} from './http-manager'
|
import {HttpManager} from './http-manager'
|
||||||
import {UploadSpecification} from './upload-specification'
|
import {UploadSpecification} from './upload-specification'
|
||||||
import {UploadOptions} from './upload-options'
|
import {UploadOptions} from './upload-options'
|
||||||
|
@ -421,7 +420,7 @@ export class UploadHttpClient {
|
||||||
digest
|
digest
|
||||||
)
|
)
|
||||||
|
|
||||||
const uploadChunkRequest = async (): Promise<IHttpClientResponse> => {
|
const uploadChunkRequest = async (): Promise<HttpClientResponse> => {
|
||||||
const client = this.uploadHttpManager.getClient(httpClientIndex)
|
const client = this.uploadHttpManager.getClient(httpClientIndex)
|
||||||
return await client.sendStream('PUT', resourceUrl, openStream(), headers)
|
return await client.sendStream('PUT', resourceUrl, openStream(), headers)
|
||||||
}
|
}
|
||||||
|
@ -432,7 +431,7 @@ export class UploadHttpClient {
|
||||||
// Increments the current retry count and then checks if the retry limit has been reached
|
// Increments the current retry count and then checks if the retry limit has been reached
|
||||||
// If there have been too many retries, fail so the download stops
|
// If there have been too many retries, fail so the download stops
|
||||||
const incrementAndCheckRetryLimit = (
|
const incrementAndCheckRetryLimit = (
|
||||||
response?: IHttpClientResponse
|
response?: HttpClientResponse
|
||||||
): boolean => {
|
): boolean => {
|
||||||
retryCount++
|
retryCount++
|
||||||
if (retryCount > retryLimit) {
|
if (retryCount > retryLimit) {
|
||||||
|
@ -469,7 +468,7 @@ export class UploadHttpClient {
|
||||||
|
|
||||||
// allow for failed chunks to be retried multiple times
|
// allow for failed chunks to be retried multiple times
|
||||||
while (retryCount <= retryLimit) {
|
while (retryCount <= retryLimit) {
|
||||||
let response: IHttpClientResponse
|
let response: HttpClientResponse
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = await uploadChunkRequest()
|
response = await uploadChunkRequest()
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import crypto from 'crypto'
|
import crypto from 'crypto'
|
||||||
import {promises as fs} from 'fs'
|
import {promises as fs} from 'fs'
|
||||||
import {IncomingHttpHeaders} from 'http'
|
import {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'
|
||||||
import {debug, info, warning} from '@actions/core'
|
import {debug, info, warning} from '@actions/core'
|
||||||
import {HttpCodes, HttpClient} from '@actions/http-client'
|
import {HttpCodes, HttpClient, HttpClientResponse} from '@actions/http-client'
|
||||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||||
import {IHeaders, IHttpClientResponse} from '@actions/http-client/interfaces'
|
|
||||||
import {
|
import {
|
||||||
getRuntimeToken,
|
getRuntimeToken,
|
||||||
getRuntimeUrl,
|
getRuntimeUrl,
|
||||||
|
@ -141,8 +140,8 @@ export function getDownloadHeaders(
|
||||||
contentType: string,
|
contentType: string,
|
||||||
isKeepAlive?: boolean,
|
isKeepAlive?: boolean,
|
||||||
acceptGzip?: boolean
|
acceptGzip?: boolean
|
||||||
): IHeaders {
|
): OutgoingHttpHeaders {
|
||||||
const requestOptions: IHeaders = {}
|
const requestOptions: OutgoingHttpHeaders = {}
|
||||||
|
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
requestOptions['Content-Type'] = contentType
|
requestOptions['Content-Type'] = contentType
|
||||||
|
@ -184,8 +183,8 @@ export function getUploadHeaders(
|
||||||
contentLength?: number,
|
contentLength?: number,
|
||||||
contentRange?: string,
|
contentRange?: string,
|
||||||
digest?: StreamDigest
|
digest?: StreamDigest
|
||||||
): IHeaders {
|
): OutgoingHttpHeaders {
|
||||||
const requestOptions: IHeaders = {}
|
const requestOptions: OutgoingHttpHeaders = {}
|
||||||
requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`
|
requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
requestOptions['Content-Type'] = contentType
|
requestOptions['Content-Type'] = contentType
|
||||||
|
@ -234,7 +233,7 @@ export function getArtifactUrl(): string {
|
||||||
* Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided.
|
* Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided.
|
||||||
* Other information such as the headers, the response code and message might be useful, so this is displayed.
|
* Other information such as the headers, the response code and message might be useful, so this is displayed.
|
||||||
*/
|
*/
|
||||||
export function displayHttpDiagnostics(response: IHttpClientResponse): void {
|
export function displayHttpDiagnostics(response: HttpClientResponse): void {
|
||||||
info(
|
info(
|
||||||
`##### Begin Diagnostic HTTP information #####
|
`##### Begin Diagnostic HTTP information #####
|
||||||
Status Code: ${response.message.statusCode}
|
Status Code: ${response.message.statusCode}
|
||||||
|
|
|
@ -56,3 +56,9 @@
|
||||||
|
|
||||||
### 2.0.0
|
### 2.0.0
|
||||||
- Added support to check if Actions cache service feature is available or not [#1028](https://github.com/actions/toolkit/pull/1028)
|
- Added support to check if Actions cache service feature is available or not [#1028](https://github.com/actions/toolkit/pull/1028)
|
||||||
|
|
||||||
|
### 2.0.3
|
||||||
|
- Update to v2.0.0 of `@actions/http-client`
|
||||||
|
|
||||||
|
### 2.0.4
|
||||||
|
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
|
@ -5,7 +5,7 @@ import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
||||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||||
import * as tar from '../src/internal/tar'
|
import * as tar from '../src/internal/tar'
|
||||||
import {ITypedResponse} from '@actions/http-client/interfaces'
|
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
||||||
import {
|
import {
|
||||||
ReserveCacheResponse,
|
ReserveCacheResponse,
|
||||||
ITypedResponseWithError
|
ITypedResponseWithError
|
||||||
|
@ -172,7 +172,7 @@ test('save with reserve cache failure should fail', async () => {
|
||||||
const reserveCacheMock = jest
|
const reserveCacheMock = jest
|
||||||
.spyOn(cacheHttpClient, 'reserveCache')
|
.spyOn(cacheHttpClient, 'reserveCache')
|
||||||
.mockImplementation(async () => {
|
.mockImplementation(async () => {
|
||||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
const response: TypedResponse<ReserveCacheResponse> = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
result: null,
|
result: null,
|
||||||
headers: {}
|
headers: {}
|
||||||
|
@ -208,7 +208,7 @@ test('save with server error should fail', async () => {
|
||||||
const reserveCacheMock = jest
|
const reserveCacheMock = jest
|
||||||
.spyOn(cacheHttpClient, 'reserveCache')
|
.spyOn(cacheHttpClient, 'reserveCache')
|
||||||
.mockImplementation(async () => {
|
.mockImplementation(async () => {
|
||||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
const response: TypedResponse<ReserveCacheResponse> = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
result: {cacheId},
|
result: {cacheId},
|
||||||
headers: {}
|
headers: {}
|
||||||
|
@ -257,7 +257,7 @@ test('save with valid inputs uploads a cache', async () => {
|
||||||
const reserveCacheMock = jest
|
const reserveCacheMock = jest
|
||||||
.spyOn(cacheHttpClient, 'reserveCache')
|
.spyOn(cacheHttpClient, 'reserveCache')
|
||||||
.mockImplementation(async () => {
|
.mockImplementation(async () => {
|
||||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
const response: TypedResponse<ReserveCacheResponse> = {
|
||||||
statusCode: 500,
|
statusCode: 500,
|
||||||
result: {cacheId},
|
result: {cacheId},
|
||||||
headers: {}
|
headers: {}
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/cache",
|
"name": "@actions/cache",
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/cache",
|
"name": "@actions/cache",
|
||||||
"version": "2.0.0",
|
"version": "2.0.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.1",
|
"@actions/exec": "^1.0.1",
|
||||||
"@actions/glob": "^0.1.0",
|
"@actions/glob": "^0.1.0",
|
||||||
"@actions/http-client": "^1.0.9",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.0.1",
|
"@actions/io": "^1.0.1",
|
||||||
"@azure/ms-rest-js": "^2.6.0",
|
"@azure/ms-rest-js": "^2.6.0",
|
||||||
"@azure/storage-blob": "^12.8.0",
|
"@azure/storage-blob": "^12.8.0",
|
||||||
|
@ -31,9 +31,9 @@
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "1.0.4",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
|
@ -48,25 +48,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "1.0.9",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@actions/http-client/node_modules/tunnel": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||||
},
|
},
|
||||||
"node_modules/@azure/abort-controller": {
|
"node_modules/@azure/abort-controller": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
@ -618,9 +610,9 @@
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||||
},
|
},
|
||||||
"@actions/exec": {
|
"@actions/exec": {
|
||||||
"version": "1.0.4",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
|
@ -635,24 +627,17 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.9",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"tunnel": {
|
|
||||||
"version": "0.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
|
||||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/io": {
|
"@actions/io": {
|
||||||
"version": "1.0.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||||
},
|
},
|
||||||
"@azure/abort-controller": {
|
"@azure/abort-controller": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/cache",
|
"name": "@actions/cache",
|
||||||
"version": "2.0.2",
|
"version": "2.0.4",
|
||||||
"preview": true,
|
"preview": true,
|
||||||
"description": "Actions cache lib",
|
"description": "Actions cache lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.1",
|
"@actions/exec": "^1.0.1",
|
||||||
"@actions/glob": "^0.1.0",
|
"@actions/glob": "^0.1.0",
|
||||||
"@actions/http-client": "^1.0.9",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.0.1",
|
"@actions/io": "^1.0.1",
|
||||||
"@azure/ms-rest-js": "^2.6.0",
|
"@azure/ms-rest-js": "^2.6.0",
|
||||||
"@azure/storage-blob": "^12.8.0",
|
"@azure/storage-blob": "^12.8.0",
|
||||||
|
@ -48,8 +48,8 @@
|
||||||
"uuid": "^3.3.3"
|
"uuid": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^3.8.3",
|
|
||||||
"@types/semver": "^6.0.0",
|
"@types/semver": "^6.0.0",
|
||||||
"@types/uuid": "^3.4.5"
|
"@types/uuid": "^3.4.5",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {HttpClient} from '@actions/http-client'
|
import {HttpClient} from '@actions/http-client'
|
||||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||||
import {IRequestOptions, ITypedResponse} from '@actions/http-client/interfaces'
|
import {
|
||||||
|
RequestOptions,
|
||||||
|
TypedResponse
|
||||||
|
} from '@actions/http-client/lib/interfaces'
|
||||||
import * as crypto from 'crypto'
|
import * as crypto from 'crypto'
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import {URL} from 'url'
|
import {URL} from 'url'
|
||||||
|
@ -46,8 +49,8 @@ function createAcceptHeader(type: string, apiVersion: string): string {
|
||||||
return `${type};api-version=${apiVersion}`
|
return `${type};api-version=${apiVersion}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getRequestOptions(): IRequestOptions {
|
function getRequestOptions(): RequestOptions {
|
||||||
const requestOptions: IRequestOptions = {
|
const requestOptions: RequestOptions = {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: createAcceptHeader('application/json', '6.0-preview.1')
|
Accept: createAcceptHeader('application/json', '6.0-preview.1')
|
||||||
}
|
}
|
||||||
|
@ -275,7 +278,7 @@ async function commitCache(
|
||||||
httpClient: HttpClient,
|
httpClient: HttpClient,
|
||||||
cacheId: number,
|
cacheId: number,
|
||||||
filesize: number
|
filesize: number
|
||||||
): Promise<ITypedResponse<null>> {
|
): Promise<TypedResponse<null>> {
|
||||||
const commitCacheRequest: CommitCacheRequest = {size: filesize}
|
const commitCacheRequest: CommitCacheRequest = {size: filesize}
|
||||||
return await retryTypedResponse('commitCache', async () =>
|
return await retryTypedResponse('commitCache', async () =>
|
||||||
httpClient.postJson<null>(
|
httpClient.postJson<null>(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {CompressionMethod} from './constants'
|
import {CompressionMethod} from './constants'
|
||||||
import {ITypedResponse} from '@actions/http-client/interfaces'
|
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
||||||
import {HttpClientError} from '@actions/http-client'
|
import {HttpClientError} from '@actions/http-client'
|
||||||
|
|
||||||
export interface ITypedResponseWithError<T> extends ITypedResponse<T> {
|
export interface ITypedResponseWithError<T> extends TypedResponse<T> {
|
||||||
error?: HttpClientError
|
error?: HttpClientError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {HttpClient} from '@actions/http-client'
|
import {HttpClient, HttpClientResponse} from '@actions/http-client'
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
|
||||||
import {BlockBlobClient} from '@azure/storage-blob'
|
import {BlockBlobClient} from '@azure/storage-blob'
|
||||||
import {TransferProgressEvent} from '@azure/ms-rest-js'
|
import {TransferProgressEvent} from '@azure/ms-rest-js'
|
||||||
import * as buffer from 'buffer'
|
import * as buffer from 'buffer'
|
||||||
|
@ -20,7 +19,7 @@ import {retryHttpClientResponse} from './requestUtils'
|
||||||
* @param output the writable stream
|
* @param output the writable stream
|
||||||
*/
|
*/
|
||||||
async function pipeResponseToStream(
|
async function pipeResponseToStream(
|
||||||
response: IHttpClientResponse,
|
response: HttpClientResponse,
|
||||||
output: NodeJS.WritableStream
|
output: NodeJS.WritableStream
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const pipeline = util.promisify(stream.pipeline)
|
const pipeline = util.promisify(stream.pipeline)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import * as core from '@actions/core'
|
import * as core from '@actions/core'
|
||||||
import {HttpCodes, HttpClientError} from '@actions/http-client'
|
import {
|
||||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
HttpCodes,
|
||||||
|
HttpClientError,
|
||||||
|
HttpClientResponse
|
||||||
|
} from '@actions/http-client'
|
||||||
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
|
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
|
||||||
import {ITypedResponseWithError} from './contracts'
|
import {ITypedResponseWithError} from './contracts'
|
||||||
|
|
||||||
|
@ -103,7 +106,7 @@ export async function retryTypedResponse<T>(
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
delay,
|
delay,
|
||||||
// If the error object contains the statusCode property, extract it and return
|
// If the error object contains the statusCode property, extract it and return
|
||||||
// an ITypedResponse<T> so it can be processed by the retry logic.
|
// an TypedResponse<T> so it can be processed by the retry logic.
|
||||||
(error: Error) => {
|
(error: Error) => {
|
||||||
if (error instanceof HttpClientError) {
|
if (error instanceof HttpClientError) {
|
||||||
return {
|
return {
|
||||||
|
@ -121,14 +124,14 @@ export async function retryTypedResponse<T>(
|
||||||
|
|
||||||
export async function retryHttpClientResponse(
|
export async function retryHttpClientResponse(
|
||||||
name: string,
|
name: string,
|
||||||
method: () => Promise<IHttpClientResponse>,
|
method: () => Promise<HttpClientResponse>,
|
||||||
maxAttempts = DefaultRetryAttempts,
|
maxAttempts = DefaultRetryAttempts,
|
||||||
delay = DefaultRetryDelay
|
delay = DefaultRetryDelay
|
||||||
): Promise<IHttpClientResponse> {
|
): Promise<HttpClientResponse> {
|
||||||
return await retry(
|
return await retry(
|
||||||
name,
|
name,
|
||||||
method,
|
method,
|
||||||
(response: IHttpClientResponse) => response.message.statusCode,
|
(response: HttpClientResponse) => response.message.statusCode,
|
||||||
maxAttempts,
|
maxAttempts,
|
||||||
delay
|
delay
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
# @actions/core Releases
|
# @actions/core Releases
|
||||||
|
|
||||||
|
### 1.8.2
|
||||||
|
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||||
|
|
||||||
|
### 1.8.1
|
||||||
|
- Update to v2.0.0 of `@actions/http-client`
|
||||||
|
|
||||||
|
### 1.8.0
|
||||||
|
- Deprecate `markdownSummary` extension export in favor of `summary`
|
||||||
|
- https://github.com/actions/toolkit/pull/1072
|
||||||
|
- https://github.com/actions/toolkit/pull/1073
|
||||||
|
|
||||||
### 1.7.0
|
### 1.7.0
|
||||||
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
|
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import * as fs from 'fs'
|
import * as fs from 'fs'
|
||||||
import * as os from 'os'
|
import * as os from 'os'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import {markdownSummary, SUMMARY_ENV_VAR} from '../src/markdown-summary'
|
import {summary, SUMMARY_ENV_VAR} from '../src/summary'
|
||||||
|
|
||||||
const testFilePath = path.join(__dirname, 'test', 'test-summary.md')
|
const testDirectoryPath = path.join(__dirname, 'test')
|
||||||
|
const testFilePath = path.join(testDirectoryPath, 'test-summary.md')
|
||||||
|
|
||||||
async function assertSummary(expected: string): Promise<void> {
|
async function assertSummary(expected: string): Promise<void> {
|
||||||
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
|
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
|
||||||
|
@ -67,11 +68,12 @@ const fixtures = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('@actions/core/src/markdown-summary', () => {
|
describe('@actions/core/src/summary', () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
process.env[SUMMARY_ENV_VAR] = testFilePath
|
process.env[SUMMARY_ENV_VAR] = testFilePath
|
||||||
|
await fs.promises.mkdir(testDirectoryPath, {recursive: true})
|
||||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||||
markdownSummary.emptyBuffer()
|
summary.emptyBuffer()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
@ -80,39 +82,39 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
|
|
||||||
it('throws if summary env var is undefined', async () => {
|
it('throws if summary env var is undefined', async () => {
|
||||||
process.env[SUMMARY_ENV_VAR] = undefined
|
process.env[SUMMARY_ENV_VAR] = undefined
|
||||||
const write = markdownSummary.addRaw(fixtures.text).write()
|
const write = summary.addRaw(fixtures.text).write()
|
||||||
|
|
||||||
await expect(write).rejects.toThrow()
|
await expect(write).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('throws if summary file does not exist', async () => {
|
it('throws if summary file does not exist', async () => {
|
||||||
await fs.promises.unlink(testFilePath)
|
await fs.promises.unlink(testFilePath)
|
||||||
const write = markdownSummary.addRaw(fixtures.text).write()
|
const write = summary.addRaw(fixtures.text).write()
|
||||||
|
|
||||||
await expect(write).rejects.toThrow()
|
await expect(write).rejects.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('appends text to summary file', async () => {
|
it('appends text to summary file', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
||||||
await markdownSummary.addRaw(fixtures.text).write()
|
await summary.addRaw(fixtures.text).write()
|
||||||
await assertSummary(`# ${fixtures.text}`)
|
await assertSummary(`# ${fixtures.text}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('overwrites text to summary file', async () => {
|
it('overwrites text to summary file', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
|
||||||
await markdownSummary.addRaw(fixtures.text).write({overwrite: true})
|
await summary.addRaw(fixtures.text).write({overwrite: true})
|
||||||
await assertSummary(fixtures.text)
|
await assertSummary(fixtures.text)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('appends text with EOL to summary file', async () => {
|
it('appends text with EOL to summary file', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
||||||
await markdownSummary.addRaw(fixtures.text, true).write()
|
await summary.addRaw(fixtures.text, true).write()
|
||||||
await assertSummary(`# ${fixtures.text}${os.EOL}`)
|
await assertSummary(`# ${fixtures.text}${os.EOL}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('chains appends text to summary file', async () => {
|
it('chains appends text to summary file', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||||
await markdownSummary
|
await summary
|
||||||
.addRaw(fixtures.text)
|
.addRaw(fixtures.text)
|
||||||
.addRaw(fixtures.text)
|
.addRaw(fixtures.text)
|
||||||
.addRaw(fixtures.text)
|
.addRaw(fixtures.text)
|
||||||
|
@ -122,33 +124,33 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
|
|
||||||
it('empties buffer after write', async () => {
|
it('empties buffer after write', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||||
await markdownSummary.addRaw(fixtures.text).write()
|
await summary.addRaw(fixtures.text).write()
|
||||||
await assertSummary(fixtures.text)
|
await assertSummary(fixtures.text)
|
||||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
expect(summary.isEmptyBuffer()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('returns summary buffer as string', () => {
|
it('returns summary buffer as string', () => {
|
||||||
markdownSummary.addRaw(fixtures.text)
|
summary.addRaw(fixtures.text)
|
||||||
expect(markdownSummary.stringify()).toEqual(fixtures.text)
|
expect(summary.stringify()).toEqual(fixtures.text)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('return correct values for isEmptyBuffer', () => {
|
it('return correct values for isEmptyBuffer', () => {
|
||||||
markdownSummary.addRaw(fixtures.text)
|
summary.addRaw(fixtures.text)
|
||||||
expect(markdownSummary.isEmptyBuffer()).toBe(false)
|
expect(summary.isEmptyBuffer()).toBe(false)
|
||||||
|
|
||||||
markdownSummary.emptyBuffer()
|
summary.emptyBuffer()
|
||||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
expect(summary.isEmptyBuffer()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('clears a buffer and summary file', async () => {
|
it('clears a buffer and summary file', async () => {
|
||||||
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
|
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
|
||||||
await markdownSummary.clear()
|
await summary.clear()
|
||||||
await assertSummary('')
|
await assertSummary('')
|
||||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
expect(summary.isEmptyBuffer()).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds EOL', async () => {
|
it('adds EOL', async () => {
|
||||||
await markdownSummary
|
await summary
|
||||||
.addRaw(fixtures.text)
|
.addRaw(fixtures.text)
|
||||||
.addEOL()
|
.addEOL()
|
||||||
.write()
|
.write()
|
||||||
|
@ -156,37 +158,37 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a code block without language', async () => {
|
it('adds a code block without language', async () => {
|
||||||
await markdownSummary.addCodeBlock(fixtures.code).write()
|
await summary.addCodeBlock(fixtures.code).write()
|
||||||
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a code block with a language', async () => {
|
it('adds a code block with a language', async () => {
|
||||||
await markdownSummary.addCodeBlock(fixtures.code, 'go').write()
|
await summary.addCodeBlock(fixtures.code, 'go').write()
|
||||||
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds an unordered list', async () => {
|
it('adds an unordered list', async () => {
|
||||||
await markdownSummary.addList(fixtures.list).write()
|
await summary.addList(fixtures.list).write()
|
||||||
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
|
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds an ordered list', async () => {
|
it('adds an ordered list', async () => {
|
||||||
await markdownSummary.addList(fixtures.list, true).write()
|
await summary.addList(fixtures.list, true).write()
|
||||||
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
|
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a table', async () => {
|
it('adds a table', async () => {
|
||||||
await markdownSummary.addTable(fixtures.table).write()
|
await summary.addTable(fixtures.table).write()
|
||||||
const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}`
|
const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a details element', async () => {
|
it('adds a details element', async () => {
|
||||||
await markdownSummary
|
await summary
|
||||||
.addDetails(fixtures.details.label, fixtures.details.content)
|
.addDetails(fixtures.details.label, fixtures.details.content)
|
||||||
.write()
|
.write()
|
||||||
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
|
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
|
||||||
|
@ -194,13 +196,13 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds an image with alt text', async () => {
|
it('adds an image with alt text', async () => {
|
||||||
await markdownSummary.addImage(fixtures.img.src, fixtures.img.alt).write()
|
await summary.addImage(fixtures.img.src, fixtures.img.alt).write()
|
||||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
|
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds an image with custom dimensions', async () => {
|
it('adds an image with custom dimensions', async () => {
|
||||||
await markdownSummary
|
await summary
|
||||||
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
||||||
.write()
|
.write()
|
||||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
||||||
|
@ -208,7 +210,7 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds an image with custom dimensions', async () => {
|
it('adds an image with custom dimensions', async () => {
|
||||||
await markdownSummary
|
await summary
|
||||||
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
||||||
.write()
|
.write()
|
||||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
||||||
|
@ -217,21 +219,21 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
|
|
||||||
it('adds headings h1...h6', async () => {
|
it('adds headings h1...h6', async () => {
|
||||||
for (const i of [1, 2, 3, 4, 5, 6]) {
|
for (const i of [1, 2, 3, 4, 5, 6]) {
|
||||||
markdownSummary.addHeading('heading', i)
|
summary.addHeading('heading', i)
|
||||||
}
|
}
|
||||||
await markdownSummary.write()
|
await summary.write()
|
||||||
const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}`
|
const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds h1 if heading level not specified', async () => {
|
it('adds h1 if heading level not specified', async () => {
|
||||||
await markdownSummary.addHeading('heading').write()
|
await summary.addHeading('heading').write()
|
||||||
const expected = `<h1>heading</h1>${os.EOL}`
|
const expected = `<h1>heading</h1>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses h1 if heading level is garbage or out of range', async () => {
|
it('uses h1 if heading level is garbage or out of range', async () => {
|
||||||
await markdownSummary
|
await summary
|
||||||
.addHeading('heading', 'foobar')
|
.addHeading('heading', 'foobar')
|
||||||
.addHeading('heading', 1337)
|
.addHeading('heading', 1337)
|
||||||
.addHeading('heading', -1)
|
.addHeading('heading', -1)
|
||||||
|
@ -242,35 +244,31 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a separator', async () => {
|
it('adds a separator', async () => {
|
||||||
await markdownSummary.addSeparator().write()
|
await summary.addSeparator().write()
|
||||||
const expected = `<hr>${os.EOL}`
|
const expected = `<hr>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a break', async () => {
|
it('adds a break', async () => {
|
||||||
await markdownSummary.addBreak().write()
|
await summary.addBreak().write()
|
||||||
const expected = `<br>${os.EOL}`
|
const expected = `<br>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a quote', async () => {
|
it('adds a quote', async () => {
|
||||||
await markdownSummary.addQuote(fixtures.quote.text).write()
|
await summary.addQuote(fixtures.quote.text).write()
|
||||||
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
|
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a quote with citation', async () => {
|
it('adds a quote with citation', async () => {
|
||||||
await markdownSummary
|
await summary.addQuote(fixtures.quote.text, fixtures.quote.cite).write()
|
||||||
.addQuote(fixtures.quote.text, fixtures.quote.cite)
|
|
||||||
.write()
|
|
||||||
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
|
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('adds a link with href', async () => {
|
it('adds a link with href', async () => {
|
||||||
await markdownSummary
|
await summary.addLink(fixtures.link.text, fixtures.link.href).write()
|
||||||
.addLink(fixtures.link.text, fixtures.link.href)
|
|
||||||
.write()
|
|
||||||
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
|
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
|
||||||
await assertSummary(expected)
|
await assertSummary(expected)
|
||||||
})
|
})
|
|
@ -1,26 +1,26 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.7.0",
|
"version": "1.8.2",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.6.0",
|
"version": "1.8.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^1.0.11"
|
"@actions/http-client": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^12.0.2"
|
"@types/node": "^12.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/core",
|
"name": "@actions/core",
|
||||||
"version": "1.7.0",
|
"version": "1.8.2",
|
||||||
"description": "Actions core lib",
|
"description": "Actions core lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
@ -36,7 +36,7 @@
|
||||||
"url": "https://github.com/actions/toolkit/issues"
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^1.0.11"
|
"@actions/http-client": "^2.0.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^12.0.2"
|
"@types/node": "^12.0.2"
|
||||||
|
|
|
@ -361,6 +361,11 @@ export async function getIDToken(aud?: string): Promise<string> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Markdown summary exports
|
* Summary exports
|
||||||
*/
|
*/
|
||||||
export {markdownSummary} from './markdown-summary'
|
export {summary} from './summary'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated use core.summary
|
||||||
|
*/
|
||||||
|
export {markdownSummary} from './summary'
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
||||||
import * as actions_http_client from '@actions/http-client'
|
import * as actions_http_client from '@actions/http-client'
|
||||||
import {IRequestOptions} from '@actions/http-client/interfaces'
|
import {RequestOptions} from '@actions/http-client/lib/interfaces'
|
||||||
import {HttpClient} from '@actions/http-client'
|
import {HttpClient} from '@actions/http-client'
|
||||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||||
import {debug, setSecret} from './core'
|
import {debug, setSecret} from './core'
|
||||||
interface TokenResponse {
|
interface TokenResponse {
|
||||||
value?: string
|
value?: string
|
||||||
|
@ -13,7 +13,7 @@ export class OidcClient {
|
||||||
allowRetry = true,
|
allowRetry = true,
|
||||||
maxRetry = 10
|
maxRetry = 10
|
||||||
): actions_http_client.HttpClient {
|
): actions_http_client.HttpClient {
|
||||||
const requestOptions: IRequestOptions = {
|
const requestOptions: RequestOptions = {
|
||||||
allowRetries: allowRetry,
|
allowRetries: allowRetry,
|
||||||
maxRetries: maxRetry
|
maxRetries: maxRetry
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ const {access, appendFile, writeFile} = promises
|
||||||
|
|
||||||
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
|
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
|
||||||
export const SUMMARY_DOCS_URL =
|
export const SUMMARY_DOCS_URL =
|
||||||
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary'
|
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'
|
||||||
|
|
||||||
export type SummaryTableRow = (SummaryTableCell | string)[]
|
export type SummaryTableRow = (SummaryTableCell | string)[]
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ export interface SummaryWriteOptions {
|
||||||
overwrite?: boolean
|
overwrite?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
class MarkdownSummary {
|
class Summary {
|
||||||
private _buffer: string
|
private _buffer: string
|
||||||
private _filePath?: string
|
private _filePath?: string
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ class MarkdownSummary {
|
||||||
const pathFromEnv = process.env[SUMMARY_ENV_VAR]
|
const pathFromEnv = process.env[SUMMARY_ENV_VAR]
|
||||||
if (!pathFromEnv) {
|
if (!pathFromEnv) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`
|
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,9 +119,9 @@ class MarkdownSummary {
|
||||||
*
|
*
|
||||||
* @param {SummaryWriteOptions} [options] (optional) options for write operation
|
* @param {SummaryWriteOptions} [options] (optional) options for write operation
|
||||||
*
|
*
|
||||||
* @returns {Promise<MarkdownSummary>} markdown summary instance
|
* @returns {Promise<Summary>} summary instance
|
||||||
*/
|
*/
|
||||||
async write(options?: SummaryWriteOptions): Promise<MarkdownSummary> {
|
async write(options?: SummaryWriteOptions): Promise<Summary> {
|
||||||
const overwrite = !!options?.overwrite
|
const overwrite = !!options?.overwrite
|
||||||
const filePath = await this.filePath()
|
const filePath = await this.filePath()
|
||||||
const writeFunc = overwrite ? writeFile : appendFile
|
const writeFunc = overwrite ? writeFile : appendFile
|
||||||
|
@ -132,9 +132,9 @@ class MarkdownSummary {
|
||||||
/**
|
/**
|
||||||
* Clears the summary buffer and wipes the summary file
|
* Clears the summary buffer and wipes the summary file
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
async clear(): Promise<MarkdownSummary> {
|
async clear(): Promise<Summary> {
|
||||||
return this.emptyBuffer().write({overwrite: true})
|
return this.emptyBuffer().write({overwrite: true})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,9 +159,9 @@ class MarkdownSummary {
|
||||||
/**
|
/**
|
||||||
* Resets the summary buffer without writing to summary file
|
* Resets the summary buffer without writing to summary file
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
emptyBuffer(): MarkdownSummary {
|
emptyBuffer(): Summary {
|
||||||
this._buffer = ''
|
this._buffer = ''
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -172,9 +172,9 @@ class MarkdownSummary {
|
||||||
* @param {string} text content to add
|
* @param {string} text content to add
|
||||||
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
|
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addRaw(text: string, addEOL = false): MarkdownSummary {
|
addRaw(text: string, addEOL = false): Summary {
|
||||||
this._buffer += text
|
this._buffer += text
|
||||||
return addEOL ? this.addEOL() : this
|
return addEOL ? this.addEOL() : this
|
||||||
}
|
}
|
||||||
|
@ -182,9 +182,9 @@ class MarkdownSummary {
|
||||||
/**
|
/**
|
||||||
* Adds the operating system-specific end-of-line marker to the buffer
|
* Adds the operating system-specific end-of-line marker to the buffer
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addEOL(): MarkdownSummary {
|
addEOL(): Summary {
|
||||||
return this.addRaw(EOL)
|
return this.addRaw(EOL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,9 +194,9 @@ class MarkdownSummary {
|
||||||
* @param {string} code content to render within fenced code block
|
* @param {string} code content to render within fenced code block
|
||||||
* @param {string} lang (optional) language to syntax highlight code
|
* @param {string} lang (optional) language to syntax highlight code
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addCodeBlock(code: string, lang?: string): MarkdownSummary {
|
addCodeBlock(code: string, lang?: string): Summary {
|
||||||
const attrs = {
|
const attrs = {
|
||||||
...(lang && {lang})
|
...(lang && {lang})
|
||||||
}
|
}
|
||||||
|
@ -210,9 +210,9 @@ class MarkdownSummary {
|
||||||
* @param {string[]} items list of items to render
|
* @param {string[]} items list of items to render
|
||||||
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
|
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addList(items: string[], ordered = false): MarkdownSummary {
|
addList(items: string[], ordered = false): Summary {
|
||||||
const tag = ordered ? 'ol' : 'ul'
|
const tag = ordered ? 'ol' : 'ul'
|
||||||
const listItems = items.map(item => this.wrap('li', item)).join('')
|
const listItems = items.map(item => this.wrap('li', item)).join('')
|
||||||
const element = this.wrap(tag, listItems)
|
const element = this.wrap(tag, listItems)
|
||||||
|
@ -224,9 +224,9 @@ class MarkdownSummary {
|
||||||
*
|
*
|
||||||
* @param {SummaryTableCell[]} rows table rows
|
* @param {SummaryTableCell[]} rows table rows
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addTable(rows: SummaryTableRow[]): MarkdownSummary {
|
addTable(rows: SummaryTableRow[]): Summary {
|
||||||
const tableBody = rows
|
const tableBody = rows
|
||||||
.map(row => {
|
.map(row => {
|
||||||
const cells = row
|
const cells = row
|
||||||
|
@ -260,9 +260,9 @@ class MarkdownSummary {
|
||||||
* @param {string} label text for the closed state
|
* @param {string} label text for the closed state
|
||||||
* @param {string} content collapsable content
|
* @param {string} content collapsable content
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addDetails(label: string, content: string): MarkdownSummary {
|
addDetails(label: string, content: string): Summary {
|
||||||
const element = this.wrap('details', this.wrap('summary', label) + content)
|
const element = this.wrap('details', this.wrap('summary', label) + content)
|
||||||
return this.addRaw(element).addEOL()
|
return this.addRaw(element).addEOL()
|
||||||
}
|
}
|
||||||
|
@ -274,13 +274,9 @@ class MarkdownSummary {
|
||||||
* @param {string} alt text description of the image
|
* @param {string} alt text description of the image
|
||||||
* @param {SummaryImageOptions} options (optional) addition image attributes
|
* @param {SummaryImageOptions} options (optional) addition image attributes
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addImage(
|
addImage(src: string, alt: string, options?: SummaryImageOptions): Summary {
|
||||||
src: string,
|
|
||||||
alt: string,
|
|
||||||
options?: SummaryImageOptions
|
|
||||||
): MarkdownSummary {
|
|
||||||
const {width, height} = options || {}
|
const {width, height} = options || {}
|
||||||
const attrs = {
|
const attrs = {
|
||||||
...(width && {width}),
|
...(width && {width}),
|
||||||
|
@ -297,9 +293,9 @@ class MarkdownSummary {
|
||||||
* @param {string} text heading text
|
* @param {string} text heading text
|
||||||
* @param {number | string} [level=1] (optional) the heading level, default: 1
|
* @param {number | string} [level=1] (optional) the heading level, default: 1
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addHeading(text: string, level?: number | string): MarkdownSummary {
|
addHeading(text: string, level?: number | string): Summary {
|
||||||
const tag = `h${level}`
|
const tag = `h${level}`
|
||||||
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
|
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
|
||||||
? tag
|
? tag
|
||||||
|
@ -311,9 +307,9 @@ class MarkdownSummary {
|
||||||
/**
|
/**
|
||||||
* Adds an HTML thematic break (<hr>) to the summary buffer
|
* Adds an HTML thematic break (<hr>) to the summary buffer
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addSeparator(): MarkdownSummary {
|
addSeparator(): Summary {
|
||||||
const element = this.wrap('hr', null)
|
const element = this.wrap('hr', null)
|
||||||
return this.addRaw(element).addEOL()
|
return this.addRaw(element).addEOL()
|
||||||
}
|
}
|
||||||
|
@ -321,9 +317,9 @@ class MarkdownSummary {
|
||||||
/**
|
/**
|
||||||
* Adds an HTML line break (<br>) to the summary buffer
|
* Adds an HTML line break (<br>) to the summary buffer
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addBreak(): MarkdownSummary {
|
addBreak(): Summary {
|
||||||
const element = this.wrap('br', null)
|
const element = this.wrap('br', null)
|
||||||
return this.addRaw(element).addEOL()
|
return this.addRaw(element).addEOL()
|
||||||
}
|
}
|
||||||
|
@ -334,9 +330,9 @@ class MarkdownSummary {
|
||||||
* @param {string} text quote text
|
* @param {string} text quote text
|
||||||
* @param {string} cite (optional) citation url
|
* @param {string} cite (optional) citation url
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addQuote(text: string, cite?: string): MarkdownSummary {
|
addQuote(text: string, cite?: string): Summary {
|
||||||
const attrs = {
|
const attrs = {
|
||||||
...(cite && {cite})
|
...(cite && {cite})
|
||||||
}
|
}
|
||||||
|
@ -350,13 +346,18 @@ class MarkdownSummary {
|
||||||
* @param {string} text link text/content
|
* @param {string} text link text/content
|
||||||
* @param {string} href hyperlink
|
* @param {string} href hyperlink
|
||||||
*
|
*
|
||||||
* @returns {MarkdownSummary} markdown summary instance
|
* @returns {Summary} summary instance
|
||||||
*/
|
*/
|
||||||
addLink(text: string, href: string): MarkdownSummary {
|
addLink(text: string, href: string): Summary {
|
||||||
const element = this.wrap('a', text, {href})
|
const element = this.wrap('a', text, {href})
|
||||||
return this.addRaw(element).addEOL()
|
return this.addRaw(element).addEOL()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// singleton export
|
const _summary = new Summary()
|
||||||
export const markdownSummary = new MarkdownSummary()
|
|
||||||
|
/**
|
||||||
|
* @deprecated use `core.summary`
|
||||||
|
*/
|
||||||
|
export const markdownSummary = _summary
|
||||||
|
export const summary = _summary
|
|
@ -1,5 +1,11 @@
|
||||||
# @actions/github Releases
|
# @actions/github Releases
|
||||||
|
|
||||||
|
### 5.0.3
|
||||||
|
- - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||||
|
|
||||||
|
### 5.0.2
|
||||||
|
- Update to v2.0.0 of `@actions/http-client`
|
||||||
|
|
||||||
### 5.0.1
|
### 5.0.1
|
||||||
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
|
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
|
||||||
### 5.0.0
|
### 5.0.0
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/github",
|
"name": "@actions/github",
|
||||||
"version": "5.0.1",
|
"version": "5.0.3",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/github",
|
"name": "@actions/github",
|
||||||
"version": "5.0.1",
|
"version": "5.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^1.0.11",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@octokit/core": "^3.6.0",
|
"@octokit/core": "^3.6.0",
|
||||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||||
|
@ -19,11 +19,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@octokit/auth-token": {
|
"node_modules/@octokit/auth-token": {
|
||||||
|
@ -361,11 +361,11 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.11",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@octokit/auth-token": {
|
"@octokit/auth-token": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/github",
|
"name": "@actions/github",
|
||||||
"version": "5.0.1",
|
"version": "5.0.3",
|
||||||
"description": "Actions github lib",
|
"description": "Actions github lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"url": "https://github.com/actions/toolkit/issues"
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/http-client": "^1.0.11",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@octokit/core": "^3.6.0",
|
"@octokit/core": "^3.6.0",
|
||||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/glob",
|
"name": "@actions/glob",
|
||||||
"version": "0.2.1",
|
"version": "0.3.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
|
@ -14,9 +14,20 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.2.6",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -47,13 +58,32 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.2.6",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
|
"requires": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"requires": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"balanced-match": {
|
"balanced-match": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
@ -81,6 +111,11 @@
|
||||||
"requires": {
|
"requires": {
|
||||||
"brace-expansion": "^1.1.7"
|
"brace-expansion": "^1.1.7"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
testoutput.txt
|
||||||
|
npm-debug.log
|
|
@ -0,0 +1,21 @@
|
||||||
|
Actions Http Client for Node.js
|
||||||
|
|
||||||
|
Copyright (c) GitHub, Inc.
|
||||||
|
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||||
|
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||||
|
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,73 @@
|
||||||
|
# `@actions/http-client`
|
||||||
|
|
||||||
|
A lightweight HTTP client optimized for building actions.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- HTTP client with TypeScript generics and async/await/Promises
|
||||||
|
- Typings included!
|
||||||
|
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner
|
||||||
|
- Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+.
|
||||||
|
- Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
|
||||||
|
- Redirects supported
|
||||||
|
|
||||||
|
Features and releases [here](./RELEASES.md)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install @actions/http-client --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## Samples
|
||||||
|
|
||||||
|
See the [tests](./__tests__) for detailed examples.
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
|
||||||
|
### HTTP
|
||||||
|
|
||||||
|
The HTTP client does not throw unless truly exceptional.
|
||||||
|
|
||||||
|
* A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body.
|
||||||
|
* Redirects (3xx) will be followed by default.
|
||||||
|
|
||||||
|
See the [tests](./__tests__) for detailed examples.
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
export NODE_DEBUG=http
|
||||||
|
```
|
||||||
|
|
||||||
|
## Node support
|
||||||
|
|
||||||
|
The http-client is built using the latest LTS version of Node 12. It may work on previous node LTS versions but it's tested and officially supported on Node12+.
|
||||||
|
|
||||||
|
## Support and Versioning
|
||||||
|
|
||||||
|
We follow semver and will hold compatibility between major versions and increment the minor version with new features and capabilities (while holding compat).
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome PRs. Please create an issue and if applicable, a design before proceeding with code.
|
||||||
|
|
||||||
|
once:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
To build:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
To run all tests:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm test
|
||||||
|
```
|
|
@ -0,0 +1,39 @@
|
||||||
|
## Releases
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
- Fix an issue with missing `tunnel` dependency [#1085](https://github.com/actions/toolkit/pull/1085)
|
||||||
|
|
||||||
|
## 2.0.0
|
||||||
|
- The package is now compiled with TypeScript's [`strict` compiler setting](https://www.typescriptlang.org/tsconfig#strict). To comply with stricter rules:
|
||||||
|
- Some exported types now include `| null` or `| undefined`, matching their actual behavior.
|
||||||
|
- Types implementing the method `RequestHandler.handleAuthentication()` now throw an `Error` rather than returning `null` if they do not support handling an HTTP 401 response. Callers can still use `canHandleAuthentication()` to determine if this handling is supported or not.
|
||||||
|
- Types using `any` have been scoped to more specific types.
|
||||||
|
- Following TypeScript's naming conventions, exported interfaces no longer begin with the prefix `I-`.
|
||||||
|
- Delete the `IHttpClientResponse` interface in favor of the `HttpClientResponse` class.
|
||||||
|
- Delete the `IHeaders` interface in favor of `http.OutgoingHttpHeaders`.
|
||||||
|
- The source code of the package was moved to build with [actions/toolkit](https://github.com/actions/toolkit).
|
||||||
|
|
||||||
|
## 1.0.11
|
||||||
|
|
||||||
|
Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
|
||||||
|
|
||||||
|
## 1.0.9
|
||||||
|
Throw HttpClientError instead of a generic Error from the \<verb>Json() helper methods when the server responds with a non-successful status code.
|
||||||
|
|
||||||
|
## 1.0.8
|
||||||
|
Fixed security issue where a redirect (e.g. 302) to another domain would pass headers. The fix was to strip the authorization header if the hostname was different. More [details in PR #27](https://github.com/actions/http-client/pull/27)
|
||||||
|
|
||||||
|
## 1.0.7
|
||||||
|
Update NPM dependencies and add 429 to the list of HttpCodes
|
||||||
|
|
||||||
|
## 1.0.6
|
||||||
|
Automatically sends Content-Type and Accept application/json headers for \<verb>Json() helper methods if not set in the client or parameters.
|
||||||
|
|
||||||
|
## 1.0.5
|
||||||
|
Adds \<verb>Json() helper methods for json over http scenarios.
|
||||||
|
|
||||||
|
## 1.0.4
|
||||||
|
Started to add \<verb>Json() helper methods. Do not use this release for that. Use >= 1.0.5 since there was an issue with types.
|
||||||
|
|
||||||
|
## 1.0.1 to 1.0.3
|
||||||
|
Adds proxy support.
|
|
@ -0,0 +1,73 @@
|
||||||
|
import * as httpm from '../lib'
|
||||||
|
import * as am from '../lib/auth'
|
||||||
|
|
||||||
|
describe('auth', () => {
|
||||||
|
beforeEach(() => {})
|
||||||
|
|
||||||
|
afterEach(() => {})
|
||||||
|
|
||||||
|
it('does basic http get request with basic auth', async () => {
|
||||||
|
const bh: am.BasicCredentialHandler = new am.BasicCredentialHandler(
|
||||||
|
'johndoe',
|
||||||
|
'password'
|
||||||
|
)
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||||
|
bh
|
||||||
|
])
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
const auth: string = obj.headers.Authorization
|
||||||
|
const creds: string = Buffer.from(
|
||||||
|
auth.substring('Basic '.length),
|
||||||
|
'base64'
|
||||||
|
).toString()
|
||||||
|
expect(creds).toBe('johndoe:password')
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with pat token auth', async () => {
|
||||||
|
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
|
||||||
|
const ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
|
||||||
|
token
|
||||||
|
)
|
||||||
|
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||||
|
ph
|
||||||
|
])
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
const auth: string = obj.headers.Authorization
|
||||||
|
const creds: string = Buffer.from(
|
||||||
|
auth.substring('Basic '.length),
|
||||||
|
'base64'
|
||||||
|
).toString()
|
||||||
|
expect(creds).toBe(`PAT:${token}`)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with pat token auth', async () => {
|
||||||
|
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
|
||||||
|
const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
|
||||||
|
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||||
|
ph
|
||||||
|
])
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
const auth: string = obj.headers.Authorization
|
||||||
|
expect(auth).toBe(`Bearer ${token}`)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,374 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import * as httpm from '..'
|
||||||
|
import * as path from 'path'
|
||||||
|
import * as fs from 'fs'
|
||||||
|
|
||||||
|
const sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
|
||||||
|
|
||||||
|
interface HttpBinData {
|
||||||
|
url: string
|
||||||
|
data: any
|
||||||
|
json: any
|
||||||
|
headers: any
|
||||||
|
args?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('basics', () => {
|
||||||
|
let _http: httpm.HttpClient
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_http = new httpm.HttpClient('http-client-tests')
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {})
|
||||||
|
|
||||||
|
it('constructs', () => {
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests')
|
||||||
|
expect(http).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
// responses from httpbin return something like:
|
||||||
|
// {
|
||||||
|
// "args": {},
|
||||||
|
// "headers": {
|
||||||
|
// "Connection": "close",
|
||||||
|
// "Host": "httpbin.org",
|
||||||
|
// "User-Agent": "typed-test-client-tests"
|
||||||
|
// },
|
||||||
|
// "origin": "173.95.152.44",
|
||||||
|
// "url": "https://httpbin.org/get"
|
||||||
|
// }
|
||||||
|
|
||||||
|
it('does basic http get request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
expect(obj.headers['User-Agent']).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with no user agent', async () => {
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient()
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
expect(obj.headers['User-Agent']).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic https get request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with default headers', async () => {
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||||
|
'http-client-tests',
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.headers.Accept).toBe('application/json')
|
||||||
|
expect(obj.headers['Content-Type']).toBe('application/json')
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with merged headers', async () => {
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||||
|
'http-client-tests',
|
||||||
|
[],
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
'http://httpbin.org/get',
|
||||||
|
{
|
||||||
|
'content-type': 'application/x-www-form-urlencoded'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.headers.Accept).toBe('application/json')
|
||||||
|
expect(obj.headers['Content-Type']).toBe(
|
||||||
|
'application/x-www-form-urlencoded'
|
||||||
|
)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('pipes a get request', async () => {
|
||||||
|
return new Promise<void>(async resolve => {
|
||||||
|
const file = fs.createWriteStream(sampleFilePath)
|
||||||
|
;(await _http.get('https://httpbin.org/get')).message
|
||||||
|
.pipe(file)
|
||||||
|
.on('close', () => {
|
||||||
|
const body: string = fs.readFileSync(sampleFilePath).toString()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
resolve()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic get request with redirects', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic get request with redirects (303)', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)}&status_code=303`
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns 404 for not found get request on redirect', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://httpbin.org/status/404'
|
||||||
|
)}&status_code=303`
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(404)
|
||||||
|
await res.readBody()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not follow redirects if disabled', async () => {
|
||||||
|
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||||
|
'typed-test-client-tests',
|
||||||
|
undefined,
|
||||||
|
{allowRedirects: false}
|
||||||
|
)
|
||||||
|
const res: httpm.HttpClientResponse = await http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(302)
|
||||||
|
await res.readBody()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not pass auth with diff hostname redirects', async () => {
|
||||||
|
const headers = {
|
||||||
|
accept: 'application/json',
|
||||||
|
authorization: 'shhh'
|
||||||
|
}
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://www.httpbin.org/get'
|
||||||
|
)}`,
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
// httpbin "fixes" the casing
|
||||||
|
expect(obj.headers['Accept']).toBe('application/json')
|
||||||
|
expect(obj.headers['Authorization']).toBeUndefined()
|
||||||
|
expect(obj.headers['authorization']).toBeUndefined()
|
||||||
|
expect(obj.url).toBe('https://www.httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not pass Auth with diff hostname redirects', async () => {
|
||||||
|
const headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
Authorization: 'shhh'
|
||||||
|
}
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||||
|
'https://www.httpbin.org/get'
|
||||||
|
)}`,
|
||||||
|
headers
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
// httpbin "fixes" the casing
|
||||||
|
expect(obj.headers['Accept']).toBe('application/json')
|
||||||
|
expect(obj.headers['Authorization']).toBeUndefined()
|
||||||
|
expect(obj.headers['authorization']).toBeUndefined()
|
||||||
|
expect(obj.url).toBe('https://www.httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic head request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.head(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http delete request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.del(
|
||||||
|
'http://httpbin.org/delete'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
JSON.parse(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http post request', async () => {
|
||||||
|
const b = 'Hello World!'
|
||||||
|
const res: httpm.HttpClientResponse = await _http.post(
|
||||||
|
'http://httpbin.org/post',
|
||||||
|
b
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.data).toBe(b)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/post')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http patch request', async () => {
|
||||||
|
const b = 'Hello World!'
|
||||||
|
const res: httpm.HttpClientResponse = await _http.patch(
|
||||||
|
'http://httpbin.org/patch',
|
||||||
|
b
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.data).toBe(b)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/patch')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http options request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.options(
|
||||||
|
'http://httpbin.org'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
await res.readBody()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns 404 for not found get request', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
'http://httpbin.org/status/404'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(404)
|
||||||
|
await res.readBody()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('gets a json object', async () => {
|
||||||
|
const jsonObj = await _http.getJson<HttpBinData>('https://httpbin.org/get')
|
||||||
|
expect(jsonObj.statusCode).toBe(200)
|
||||||
|
expect(jsonObj.result).toBeDefined()
|
||||||
|
expect(jsonObj.result?.url).toBe('https://httpbin.org/get')
|
||||||
|
expect(jsonObj.result?.headers['Accept']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getting a non existent json object returns null', async () => {
|
||||||
|
const jsonObj = await _http.getJson<HttpBinData>(
|
||||||
|
'https://httpbin.org/status/404'
|
||||||
|
)
|
||||||
|
expect(jsonObj.statusCode).toBe(404)
|
||||||
|
expect(jsonObj.result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('posts a json object', async () => {
|
||||||
|
const res = {name: 'foo'}
|
||||||
|
const restRes = await _http.postJson<HttpBinData>(
|
||||||
|
'https://httpbin.org/post',
|
||||||
|
res
|
||||||
|
)
|
||||||
|
expect(restRes.statusCode).toBe(200)
|
||||||
|
expect(restRes.result).toBeDefined()
|
||||||
|
expect(restRes.result?.url).toBe('https://httpbin.org/post')
|
||||||
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
|
expect(restRes.result?.headers['Accept']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('puts a json object', async () => {
|
||||||
|
const res = {name: 'foo'}
|
||||||
|
const restRes = await _http.putJson<HttpBinData>(
|
||||||
|
'https://httpbin.org/put',
|
||||||
|
res
|
||||||
|
)
|
||||||
|
expect(restRes.statusCode).toBe(200)
|
||||||
|
expect(restRes.result).toBeDefined()
|
||||||
|
expect(restRes.result?.url).toBe('https://httpbin.org/put')
|
||||||
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
|
|
||||||
|
expect(restRes.result?.headers['Accept']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('patch a json object', async () => {
|
||||||
|
const res = {name: 'foo'}
|
||||||
|
const restRes = await _http.patchJson<HttpBinData>(
|
||||||
|
'https://httpbin.org/patch',
|
||||||
|
res
|
||||||
|
)
|
||||||
|
expect(restRes.statusCode).toBe(200)
|
||||||
|
expect(restRes.result).toBeDefined()
|
||||||
|
expect(restRes.result?.url).toBe('https://httpbin.org/patch')
|
||||||
|
expect(restRes.result?.json.name).toBe('foo')
|
||||||
|
expect(restRes.result?.headers['Accept']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,116 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import * as httpm from '..'
|
||||||
|
|
||||||
|
describe('headers', () => {
|
||||||
|
let _http: httpm.HttpClient
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_http = new httpm.HttpClient('http-client-tests')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves existing headers on getJson', async () => {
|
||||||
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
|
let jsonObj = await _http.getJson<any>(
|
||||||
|
'https://httpbin.org/get',
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpWithHeaders = new httpm.HttpClient()
|
||||||
|
httpWithHeaders.requestOptions = {
|
||||||
|
headers: {
|
||||||
|
[httpm.Headers.Accept]: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonObj = await httpWithHeaders.getJson<any>('https://httpbin.org/get')
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves existing headers on postJson', async () => {
|
||||||
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
|
let jsonObj = await _http.postJson<any>(
|
||||||
|
'https://httpbin.org/post',
|
||||||
|
{},
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpWithHeaders = new httpm.HttpClient()
|
||||||
|
httpWithHeaders.requestOptions = {
|
||||||
|
headers: {
|
||||||
|
[httpm.Headers.Accept]: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonObj = await httpWithHeaders.postJson<any>(
|
||||||
|
'https://httpbin.org/post',
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves existing headers on putJson', async () => {
|
||||||
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
|
let jsonObj = await _http.putJson<any>(
|
||||||
|
'https://httpbin.org/put',
|
||||||
|
{},
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpWithHeaders = new httpm.HttpClient()
|
||||||
|
httpWithHeaders.requestOptions = {
|
||||||
|
headers: {
|
||||||
|
[httpm.Headers.Accept]: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonObj = await httpWithHeaders.putJson<any>('https://httpbin.org/put', {})
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('preserves existing headers on patchJson', async () => {
|
||||||
|
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||||
|
let jsonObj = await _http.patchJson<any>(
|
||||||
|
'https://httpbin.org/patch',
|
||||||
|
{},
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
|
||||||
|
const httpWithHeaders = new httpm.HttpClient()
|
||||||
|
httpWithHeaders.requestOptions = {
|
||||||
|
headers: {
|
||||||
|
[httpm.Headers.Accept]: 'baz'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jsonObj = await httpWithHeaders.patchJson<any>(
|
||||||
|
'https://httpbin.org/patch',
|
||||||
|
{}
|
||||||
|
)
|
||||||
|
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||||
|
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||||
|
httpm.MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,73 @@
|
||||||
|
import * as httpm from '../lib'
|
||||||
|
|
||||||
|
describe('basics', () => {
|
||||||
|
let _http: httpm.HttpClient
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_http = new httpm.HttpClient('http-client-tests', [], {keepAlive: true})
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
_http.dispose()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http get request with keepAlive true', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic head request with keepAlive true', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.head(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http delete request with keepAlive true', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.del(
|
||||||
|
'http://httpbin.org/delete'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
JSON.parse(body)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http post request with keepAlive true', async () => {
|
||||||
|
const b = 'Hello World!'
|
||||||
|
const res: httpm.HttpClientResponse = await _http.post(
|
||||||
|
'http://httpbin.org/post',
|
||||||
|
b
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.data).toBe(b)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/post')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http patch request with keepAlive true', async () => {
|
||||||
|
const b = 'Hello World!'
|
||||||
|
const res: httpm.HttpClientResponse = await _http.patch(
|
||||||
|
'http://httpbin.org/patch',
|
||||||
|
b
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.data).toBe(b)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/patch')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does basic http options request with keepAlive true', async () => {
|
||||||
|
const res: httpm.HttpClientResponse = await _http.options(
|
||||||
|
'http://httpbin.org'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
await res.readBody()
|
||||||
|
})
|
||||||
|
})
|
|
@ -0,0 +1,232 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import * as http from 'http'
|
||||||
|
import * as httpm from '../lib/'
|
||||||
|
import * as pm from '../lib/proxy'
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||||
|
const proxy = require('proxy')
|
||||||
|
|
||||||
|
let _proxyConnects: string[]
|
||||||
|
let _proxyServer: http.Server
|
||||||
|
const _proxyUrl = 'http://127.0.0.1:8080'
|
||||||
|
|
||||||
|
describe('proxy', () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
// Start proxy server
|
||||||
|
_proxyServer = proxy()
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
const port = Number(_proxyUrl.split(':')[2])
|
||||||
|
_proxyServer.listen(port, () => resolve())
|
||||||
|
})
|
||||||
|
_proxyServer.on('connect', req => {
|
||||||
|
_proxyConnects.push(req.url)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
_proxyConnects = []
|
||||||
|
_clearVars()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {})
|
||||||
|
|
||||||
|
afterAll(async () => {
|
||||||
|
_clearVars()
|
||||||
|
|
||||||
|
// Stop proxy server
|
||||||
|
await new Promise<void>(resolve => {
|
||||||
|
_proxyServer.once('close', () => resolve())
|
||||||
|
_proxyServer.close()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl does not return proxyUrl if variables not set', () => {
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||||
|
expect(proxyUrl).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
|
||||||
|
process.env['https_proxy'] = 'https://myproxysvr'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||||
|
expect(proxyUrl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
|
||||||
|
process.env['http_proxy'] = 'https://myproxysvr'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||||
|
expect(proxyUrl).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
|
||||||
|
process.env['http_proxy'] = 'http://myproxysvr'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
|
||||||
|
expect(proxyUrl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
|
||||||
|
process.env['https_proxy'] = 'https://myproxysvr'
|
||||||
|
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
|
||||||
|
expect(proxyUrl).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
|
||||||
|
process.env['https_proxy'] = 'https://myproxysvr'
|
||||||
|
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||||
|
expect(proxyUrl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
|
||||||
|
process.env['http_proxy'] = 'http://myproxysvr'
|
||||||
|
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
|
||||||
|
expect(proxyUrl).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
|
||||||
|
process.env['http_proxy'] = 'http://myproxysvr'
|
||||||
|
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
|
||||||
|
expect(proxyUrl).toBeDefined()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host as no_proxy list', () => {
|
||||||
|
process.env['no_proxy'] = 'myserver'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host in no_proxy list', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host in no_proxy list with spaces', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host in no_proxy list with port', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host with port in no_proxy list without port', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host in no_proxy list with default https port', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns true if host in no_proxy list with default http port', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
|
||||||
|
const bypass = pm.checkBypass(new URL('http://myserver'))
|
||||||
|
expect(bypass).toBeTruthy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns false if host not in no_proxy list', () => {
|
||||||
|
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
|
||||||
|
const bypass = pm.checkBypass(new URL('https://github.com'))
|
||||||
|
expect(bypass).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checkBypass returns false if empty no_proxy', () => {
|
||||||
|
process.env['no_proxy'] = ''
|
||||||
|
const bypass = pm.checkBypass(new URL('https://github.com'))
|
||||||
|
expect(bypass).toBeFalsy()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('HttpClient does basic http get request through proxy', async () => {
|
||||||
|
process.env['http_proxy'] = _proxyUrl
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
expect(_proxyConnects).toEqual(['httpbin.org:80'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('HttoClient does basic http get request when bypass proxy', async () => {
|
||||||
|
process.env['http_proxy'] = _proxyUrl
|
||||||
|
process.env['no_proxy'] = 'httpbin.org'
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
|
'http://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('http://httpbin.org/get')
|
||||||
|
expect(_proxyConnects).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('HttpClient does basic https get request through proxy', async () => {
|
||||||
|
process.env['https_proxy'] = _proxyUrl
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
expect(_proxyConnects).toEqual(['httpbin.org:443'])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('HttpClient does basic https get request when bypass proxy', async () => {
|
||||||
|
process.env['https_proxy'] = _proxyUrl
|
||||||
|
process.env['no_proxy'] = 'httpbin.org'
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||||
|
'https://httpbin.org/get'
|
||||||
|
)
|
||||||
|
expect(res.message.statusCode).toBe(200)
|
||||||
|
const body: string = await res.readBody()
|
||||||
|
const obj = JSON.parse(body)
|
||||||
|
expect(obj.url).toBe('https://httpbin.org/get')
|
||||||
|
expect(_proxyConnects).toHaveLength(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('proxyAuth not set in tunnel agent when authentication is not provided', async () => {
|
||||||
|
process.env['https_proxy'] = 'http://127.0.0.1:8080'
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const agent: any = httpClient.getAgent('https://some-url')
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(agent)
|
||||||
|
expect(agent.proxyOptions.host).toBe('127.0.0.1')
|
||||||
|
expect(agent.proxyOptions.port).toBe('8080')
|
||||||
|
expect(agent.proxyOptions.proxyAuth).toBe(undefined)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
|
||||||
|
process.env['https_proxy'] = 'http://user:password@127.0.0.1:8080'
|
||||||
|
const httpClient = new httpm.HttpClient()
|
||||||
|
const agent: any = httpClient.getAgent('https://some-url')
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(agent)
|
||||||
|
expect(agent.proxyOptions.host).toBe('127.0.0.1')
|
||||||
|
expect(agent.proxyOptions.port).toBe('8080')
|
||||||
|
expect(agent.proxyOptions.proxyAuth).toBe('user:password')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
function _clearVars(): void {
|
||||||
|
delete process.env.http_proxy
|
||||||
|
delete process.env.HTTP_PROXY
|
||||||
|
delete process.env.https_proxy
|
||||||
|
delete process.env.HTTPS_PROXY
|
||||||
|
delete process.env.no_proxy
|
||||||
|
delete process.env.NO_PROXY
|
||||||
|
}
|
|
@ -0,0 +1,332 @@
|
||||||
|
{
|
||||||
|
"name": "@actions/http-client",
|
||||||
|
"version": "2.0.1",
|
||||||
|
"lockfileVersion": 2,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "@actions/http-client",
|
||||||
|
"version": "2.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "^0.0.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/tunnel": "0.0.3",
|
||||||
|
"proxy": "^1.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "12.12.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.31.tgz",
|
||||||
|
"integrity": "sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/tunnel": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ansi-styles": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-convert": "^1.9.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/args": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"camelcase": "5.0.0",
|
||||||
|
"chalk": "2.4.2",
|
||||||
|
"leven": "2.1.0",
|
||||||
|
"mri": "1.1.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/basic-auth-parser": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz",
|
||||||
|
"integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/camelcase": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/chalk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/leven": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mri": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/proxy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-KNac2ueWRpjbUh77OAFPZuNdfEqNynm9DD4xHT14CccGpW8wKZwEkN0yjlb7X9G9Z9F55N0Q+1z+WfgAhwYdzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"args": "5.0.1",
|
||||||
|
"basic-auth-parser": "0.0.2",
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"proxy": "bin/proxy.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/supports-color": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": {
|
||||||
|
"version": "12.12.31",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.31.tgz",
|
||||||
|
"integrity": "sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"@types/tunnel": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "3.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-convert": "^1.9.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"args": {
|
||||||
|
"version": "5.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz",
|
||||||
|
"integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"camelcase": "5.0.0",
|
||||||
|
"chalk": "2.4.2",
|
||||||
|
"leven": "2.1.0",
|
||||||
|
"mri": "1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"basic-auth-parser": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz",
|
||||||
|
"integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"camelcase": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "2.4.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^3.2.1",
|
||||||
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
"supports-color": "^5.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "1.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"color-name": "1.1.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "^2.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"escape-string-regexp": {
|
||||||
|
"version": "1.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"has-flag": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"leven": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||||
|
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"mri": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"ms": {
|
||||||
|
"version": "2.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"proxy": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-KNac2ueWRpjbUh77OAFPZuNdfEqNynm9DD4xHT14CccGpW8wKZwEkN0yjlb7X9G9Z9F55N0Q+1z+WfgAhwYdzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"args": "5.0.1",
|
||||||
|
"basic-auth-parser": "0.0.2",
|
||||||
|
"debug": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"supports-color": {
|
||||||
|
"version": "5.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"has-flag": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tunnel": {
|
||||||
|
"version": "0.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||||
|
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
{
|
||||||
|
"name": "@actions/http-client",
|
||||||
|
"version": "2.0.1",
|
||||||
|
"description": "Actions Http Client",
|
||||||
|
"keywords": [
|
||||||
|
"github",
|
||||||
|
"actions",
|
||||||
|
"http"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"types": "lib/index.d.ts",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "__tests__"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"!.DS_Store"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/actions/toolkit.git",
|
||||||
|
"directory": "packages/http-client"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
|
||||||
|
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"format": "prettier --write **/*.ts",
|
||||||
|
"format-check": "prettier --check **/*.ts",
|
||||||
|
"tsc": "tsc"
|
||||||
|
},
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/actions/toolkit/issues"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/tunnel": "0.0.3",
|
||||||
|
"proxy": "^1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "^0.0.6"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,86 @@
|
||||||
|
import * as http from 'http'
|
||||||
|
import * as ifm from './interfaces'
|
||||||
|
import {HttpClientResponse} from './index'
|
||||||
|
|
||||||
|
export class BasicCredentialHandler implements ifm.RequestHandler {
|
||||||
|
username: string
|
||||||
|
password: string
|
||||||
|
|
||||||
|
constructor(username: string, password: string) {
|
||||||
|
this.username = username
|
||||||
|
this.password = password
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareRequest(options: http.RequestOptions): void {
|
||||||
|
if (!options.headers) {
|
||||||
|
throw Error('The request has no headers')
|
||||||
|
}
|
||||||
|
options.headers['Authorization'] = `Basic ${Buffer.from(
|
||||||
|
`${this.username}:${this.password}`
|
||||||
|
).toString('base64')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handler cannot handle 401
|
||||||
|
canHandleAuthentication(): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BearerCredentialHandler implements ifm.RequestHandler {
|
||||||
|
token: string
|
||||||
|
|
||||||
|
constructor(token: string) {
|
||||||
|
this.token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently implements pre-authorization
|
||||||
|
// TODO: support preAuth = false where it hooks on 401
|
||||||
|
prepareRequest(options: http.RequestOptions): void {
|
||||||
|
if (!options.headers) {
|
||||||
|
throw Error('The request has no headers')
|
||||||
|
}
|
||||||
|
options.headers['Authorization'] = `Bearer ${this.token}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handler cannot handle 401
|
||||||
|
canHandleAuthentication(): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PersonalAccessTokenCredentialHandler
|
||||||
|
implements ifm.RequestHandler {
|
||||||
|
token: string
|
||||||
|
|
||||||
|
constructor(token: string) {
|
||||||
|
this.token = token
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently implements pre-authorization
|
||||||
|
// TODO: support preAuth = false where it hooks on 401
|
||||||
|
prepareRequest(options: http.RequestOptions): void {
|
||||||
|
if (!options.headers) {
|
||||||
|
throw Error('The request has no headers')
|
||||||
|
}
|
||||||
|
options.headers['Authorization'] = `Basic ${Buffer.from(
|
||||||
|
`PAT:${this.token}`
|
||||||
|
).toString('base64')}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// This handler cannot handle 401
|
||||||
|
canHandleAuthentication(): boolean {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||||
|
throw new Error('not implemented')
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,773 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import * as http from 'http'
|
||||||
|
import * as https from 'https'
|
||||||
|
import * as ifm from './interfaces'
|
||||||
|
import * as net from 'net'
|
||||||
|
import * as pm from './proxy'
|
||||||
|
import * as tunnel from 'tunnel'
|
||||||
|
|
||||||
|
export enum HttpCodes {
|
||||||
|
OK = 200,
|
||||||
|
MultipleChoices = 300,
|
||||||
|
MovedPermanently = 301,
|
||||||
|
ResourceMoved = 302,
|
||||||
|
SeeOther = 303,
|
||||||
|
NotModified = 304,
|
||||||
|
UseProxy = 305,
|
||||||
|
SwitchProxy = 306,
|
||||||
|
TemporaryRedirect = 307,
|
||||||
|
PermanentRedirect = 308,
|
||||||
|
BadRequest = 400,
|
||||||
|
Unauthorized = 401,
|
||||||
|
PaymentRequired = 402,
|
||||||
|
Forbidden = 403,
|
||||||
|
NotFound = 404,
|
||||||
|
MethodNotAllowed = 405,
|
||||||
|
NotAcceptable = 406,
|
||||||
|
ProxyAuthenticationRequired = 407,
|
||||||
|
RequestTimeout = 408,
|
||||||
|
Conflict = 409,
|
||||||
|
Gone = 410,
|
||||||
|
TooManyRequests = 429,
|
||||||
|
InternalServerError = 500,
|
||||||
|
NotImplemented = 501,
|
||||||
|
BadGateway = 502,
|
||||||
|
ServiceUnavailable = 503,
|
||||||
|
GatewayTimeout = 504
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Headers {
|
||||||
|
Accept = 'accept',
|
||||||
|
ContentType = 'content-type'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MediaTypes {
|
||||||
|
ApplicationJson = 'application/json'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the proxy URL, depending upon the supplied url and proxy environment variables.
|
||||||
|
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
||||||
|
*/
|
||||||
|
export function getProxyUrl(serverUrl: string): string {
|
||||||
|
const proxyUrl = pm.getProxyUrl(new URL(serverUrl))
|
||||||
|
return proxyUrl ? proxyUrl.href : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const HttpRedirectCodes: number[] = [
|
||||||
|
HttpCodes.MovedPermanently,
|
||||||
|
HttpCodes.ResourceMoved,
|
||||||
|
HttpCodes.SeeOther,
|
||||||
|
HttpCodes.TemporaryRedirect,
|
||||||
|
HttpCodes.PermanentRedirect
|
||||||
|
]
|
||||||
|
const HttpResponseRetryCodes: number[] = [
|
||||||
|
HttpCodes.BadGateway,
|
||||||
|
HttpCodes.ServiceUnavailable,
|
||||||
|
HttpCodes.GatewayTimeout
|
||||||
|
]
|
||||||
|
const RetryableHttpVerbs: string[] = ['OPTIONS', 'GET', 'DELETE', 'HEAD']
|
||||||
|
const ExponentialBackoffCeiling = 10
|
||||||
|
const ExponentialBackoffTimeSlice = 5
|
||||||
|
|
||||||
|
export class HttpClientError extends Error {
|
||||||
|
constructor(message: string, statusCode: number) {
|
||||||
|
super(message)
|
||||||
|
this.name = 'HttpClientError'
|
||||||
|
this.statusCode = statusCode
|
||||||
|
Object.setPrototypeOf(this, HttpClientError.prototype)
|
||||||
|
}
|
||||||
|
|
||||||
|
statusCode: number
|
||||||
|
result?: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpClientResponse {
|
||||||
|
constructor(message: http.IncomingMessage) {
|
||||||
|
this.message = message
|
||||||
|
}
|
||||||
|
|
||||||
|
message: http.IncomingMessage
|
||||||
|
async readBody(): Promise<string> {
|
||||||
|
return new Promise<string>(async resolve => {
|
||||||
|
let output = Buffer.alloc(0)
|
||||||
|
|
||||||
|
this.message.on('data', (chunk: Buffer) => {
|
||||||
|
output = Buffer.concat([output, chunk])
|
||||||
|
})
|
||||||
|
|
||||||
|
this.message.on('end', () => {
|
||||||
|
resolve(output.toString())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isHttps(requestUrl: string): boolean {
|
||||||
|
const parsedUrl: URL = new URL(requestUrl)
|
||||||
|
return parsedUrl.protocol === 'https:'
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HttpClient {
|
||||||
|
userAgent: string | undefined
|
||||||
|
handlers: ifm.RequestHandler[]
|
||||||
|
requestOptions: ifm.RequestOptions | undefined
|
||||||
|
|
||||||
|
private _ignoreSslError = false
|
||||||
|
private _socketTimeout: number | undefined
|
||||||
|
private _allowRedirects = true
|
||||||
|
private _allowRedirectDowngrade = false
|
||||||
|
private _maxRedirects = 50
|
||||||
|
private _allowRetries = false
|
||||||
|
private _maxRetries = 1
|
||||||
|
private _agent: any
|
||||||
|
private _proxyAgent: any
|
||||||
|
private _keepAlive = false
|
||||||
|
private _disposed = false
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
userAgent?: string,
|
||||||
|
handlers?: ifm.RequestHandler[],
|
||||||
|
requestOptions?: ifm.RequestOptions
|
||||||
|
) {
|
||||||
|
this.userAgent = userAgent
|
||||||
|
this.handlers = handlers || []
|
||||||
|
this.requestOptions = requestOptions
|
||||||
|
if (requestOptions) {
|
||||||
|
if (requestOptions.ignoreSslError != null) {
|
||||||
|
this._ignoreSslError = requestOptions.ignoreSslError
|
||||||
|
}
|
||||||
|
|
||||||
|
this._socketTimeout = requestOptions.socketTimeout
|
||||||
|
|
||||||
|
if (requestOptions.allowRedirects != null) {
|
||||||
|
this._allowRedirects = requestOptions.allowRedirects
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.allowRedirectDowngrade != null) {
|
||||||
|
this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.maxRedirects != null) {
|
||||||
|
this._maxRedirects = Math.max(requestOptions.maxRedirects, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.keepAlive != null) {
|
||||||
|
this._keepAlive = requestOptions.keepAlive
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.allowRetries != null) {
|
||||||
|
this._allowRetries = requestOptions.allowRetries
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requestOptions.maxRetries != null) {
|
||||||
|
this._maxRetries = requestOptions.maxRetries
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async options(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('GET', requestUrl, null, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async del(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('DELETE', requestUrl, null, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('POST', requestUrl, data, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async patch(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('PATCH', requestUrl, data, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('PUT', requestUrl, data, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async head(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request('HEAD', requestUrl, null, additionalHeaders || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendStream(
|
||||||
|
verb: string,
|
||||||
|
requestUrl: string,
|
||||||
|
stream: NodeJS.ReadableStream,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return this.request(verb, requestUrl, stream, additionalHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a typed object from an endpoint
|
||||||
|
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
|
||||||
|
*/
|
||||||
|
async getJson<T>(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||||
|
): Promise<ifm.TypedResponse<T>> {
|
||||||
|
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.Accept,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
const res: HttpClientResponse = await this.get(
|
||||||
|
requestUrl,
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
return this._processResponse<T>(res, this.requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async postJson<T>(
|
||||||
|
requestUrl: string,
|
||||||
|
obj: any,
|
||||||
|
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||||
|
): Promise<ifm.TypedResponse<T>> {
|
||||||
|
const data: string = JSON.stringify(obj, null, 2)
|
||||||
|
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.Accept,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.ContentType,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
const res: HttpClientResponse = await this.post(
|
||||||
|
requestUrl,
|
||||||
|
data,
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
return this._processResponse<T>(res, this.requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async putJson<T>(
|
||||||
|
requestUrl: string,
|
||||||
|
obj: any,
|
||||||
|
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||||
|
): Promise<ifm.TypedResponse<T>> {
|
||||||
|
const data: string = JSON.stringify(obj, null, 2)
|
||||||
|
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.Accept,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.ContentType,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
const res: HttpClientResponse = await this.put(
|
||||||
|
requestUrl,
|
||||||
|
data,
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
return this._processResponse<T>(res, this.requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
async patchJson<T>(
|
||||||
|
requestUrl: string,
|
||||||
|
obj: any,
|
||||||
|
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||||
|
): Promise<ifm.TypedResponse<T>> {
|
||||||
|
const data: string = JSON.stringify(obj, null, 2)
|
||||||
|
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.Accept,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders,
|
||||||
|
Headers.ContentType,
|
||||||
|
MediaTypes.ApplicationJson
|
||||||
|
)
|
||||||
|
const res: HttpClientResponse = await this.patch(
|
||||||
|
requestUrl,
|
||||||
|
data,
|
||||||
|
additionalHeaders
|
||||||
|
)
|
||||||
|
return this._processResponse<T>(res, this.requestOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes a raw http request.
|
||||||
|
* All other methods such as get, post, patch, and request ultimately call this.
|
||||||
|
* Prefer get, del, post and patch
|
||||||
|
*/
|
||||||
|
async request(
|
||||||
|
verb: string,
|
||||||
|
requestUrl: string,
|
||||||
|
data: string | NodeJS.ReadableStream | null,
|
||||||
|
headers?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
if (this._disposed) {
|
||||||
|
throw new Error('Client has already been disposed.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedUrl = new URL(requestUrl)
|
||||||
|
let info: ifm.RequestInfo = this._prepareRequest(verb, parsedUrl, headers)
|
||||||
|
|
||||||
|
// Only perform retries on reads since writes may not be idempotent.
|
||||||
|
const maxTries: number =
|
||||||
|
this._allowRetries && RetryableHttpVerbs.includes(verb)
|
||||||
|
? this._maxRetries + 1
|
||||||
|
: 1
|
||||||
|
let numTries = 0
|
||||||
|
|
||||||
|
let response: HttpClientResponse | undefined
|
||||||
|
do {
|
||||||
|
response = await this.requestRaw(info, data)
|
||||||
|
|
||||||
|
// Check if it's an authentication challenge
|
||||||
|
if (
|
||||||
|
response &&
|
||||||
|
response.message &&
|
||||||
|
response.message.statusCode === HttpCodes.Unauthorized
|
||||||
|
) {
|
||||||
|
let authenticationHandler: ifm.RequestHandler | undefined
|
||||||
|
|
||||||
|
for (const handler of this.handlers) {
|
||||||
|
if (handler.canHandleAuthentication(response)) {
|
||||||
|
authenticationHandler = handler
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authenticationHandler) {
|
||||||
|
return authenticationHandler.handleAuthentication(this, info, data)
|
||||||
|
} else {
|
||||||
|
// We have received an unauthorized response but have no handlers to handle it.
|
||||||
|
// Let the response return to the caller.
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let redirectsRemaining: number = this._maxRedirects
|
||||||
|
while (
|
||||||
|
response.message.statusCode &&
|
||||||
|
HttpRedirectCodes.includes(response.message.statusCode) &&
|
||||||
|
this._allowRedirects &&
|
||||||
|
redirectsRemaining > 0
|
||||||
|
) {
|
||||||
|
const redirectUrl: string | undefined =
|
||||||
|
response.message.headers['location']
|
||||||
|
if (!redirectUrl) {
|
||||||
|
// if there's no location to redirect to, we won't
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const parsedRedirectUrl = new URL(redirectUrl)
|
||||||
|
if (
|
||||||
|
parsedUrl.protocol === 'https:' &&
|
||||||
|
parsedUrl.protocol !== parsedRedirectUrl.protocol &&
|
||||||
|
!this._allowRedirectDowngrade
|
||||||
|
) {
|
||||||
|
throw new Error(
|
||||||
|
'Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// we need to finish reading the response before reassigning response
|
||||||
|
// which will leak the open socket.
|
||||||
|
await response.readBody()
|
||||||
|
|
||||||
|
// strip authorization header if redirected to a different hostname
|
||||||
|
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
|
||||||
|
for (const header in headers) {
|
||||||
|
// header names are case insensitive
|
||||||
|
if (header.toLowerCase() === 'authorization') {
|
||||||
|
delete headers[header]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// let's make the request with the new redirectUrl
|
||||||
|
info = this._prepareRequest(verb, parsedRedirectUrl, headers)
|
||||||
|
response = await this.requestRaw(info, data)
|
||||||
|
redirectsRemaining--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!response.message.statusCode ||
|
||||||
|
!HttpResponseRetryCodes.includes(response.message.statusCode)
|
||||||
|
) {
|
||||||
|
// If not a retry code, return immediately instead of retrying
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
numTries += 1
|
||||||
|
|
||||||
|
if (numTries < maxTries) {
|
||||||
|
await response.readBody()
|
||||||
|
await this._performExponentialBackoff(numTries)
|
||||||
|
}
|
||||||
|
} while (numTries < maxTries)
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Needs to be called if keepAlive is set to true in request options.
|
||||||
|
*/
|
||||||
|
dispose(): void {
|
||||||
|
if (this._agent) {
|
||||||
|
this._agent.destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw request.
|
||||||
|
* @param info
|
||||||
|
* @param data
|
||||||
|
*/
|
||||||
|
async requestRaw(
|
||||||
|
info: ifm.RequestInfo,
|
||||||
|
data: string | NodeJS.ReadableStream | null
|
||||||
|
): Promise<HttpClientResponse> {
|
||||||
|
return new Promise<HttpClientResponse>((resolve, reject) => {
|
||||||
|
function callbackForResult(err?: Error, res?: HttpClientResponse): void {
|
||||||
|
if (err) {
|
||||||
|
reject(err)
|
||||||
|
} else if (!res) {
|
||||||
|
// If `err` is not passed, then `res` must be passed.
|
||||||
|
reject(new Error('Unknown error'))
|
||||||
|
} else {
|
||||||
|
resolve(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.requestRawWithCallback(info, data, callbackForResult)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Raw request with callback.
|
||||||
|
* @param info
|
||||||
|
* @param data
|
||||||
|
* @param onResult
|
||||||
|
*/
|
||||||
|
requestRawWithCallback(
|
||||||
|
info: ifm.RequestInfo,
|
||||||
|
data: string | NodeJS.ReadableStream | null,
|
||||||
|
onResult: (err?: Error, res?: HttpClientResponse) => void
|
||||||
|
): void {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
if (!info.options.headers) {
|
||||||
|
info.options.headers = {}
|
||||||
|
}
|
||||||
|
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
let callbackCalled = false
|
||||||
|
function handleResult(err?: Error, res?: HttpClientResponse): void {
|
||||||
|
if (!callbackCalled) {
|
||||||
|
callbackCalled = true
|
||||||
|
onResult(err, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const req: http.ClientRequest = info.httpModule.request(
|
||||||
|
info.options,
|
||||||
|
(msg: http.IncomingMessage) => {
|
||||||
|
const res: HttpClientResponse = new HttpClientResponse(msg)
|
||||||
|
handleResult(undefined, res)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
let socket: net.Socket
|
||||||
|
req.on('socket', sock => {
|
||||||
|
socket = sock
|
||||||
|
})
|
||||||
|
|
||||||
|
// If we ever get disconnected, we want the socket to timeout eventually
|
||||||
|
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
|
||||||
|
if (socket) {
|
||||||
|
socket.end()
|
||||||
|
}
|
||||||
|
handleResult(new Error(`Request timeout: ${info.options.path}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
req.on('error', function(err) {
|
||||||
|
// err has statusCode property
|
||||||
|
// res should have headers
|
||||||
|
handleResult(err)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (data && typeof data === 'string') {
|
||||||
|
req.write(data, 'utf8')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data && typeof data !== 'string') {
|
||||||
|
data.on('close', function() {
|
||||||
|
req.end()
|
||||||
|
})
|
||||||
|
|
||||||
|
data.pipe(req)
|
||||||
|
} else {
|
||||||
|
req.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets an http agent. This function is useful when you need an http agent that handles
|
||||||
|
* routing through a proxy server - depending upon the url and proxy environment variables.
|
||||||
|
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
||||||
|
*/
|
||||||
|
getAgent(serverUrl: string): http.Agent {
|
||||||
|
const parsedUrl = new URL(serverUrl)
|
||||||
|
return this._getAgent(parsedUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
private _prepareRequest(
|
||||||
|
method: string,
|
||||||
|
requestUrl: URL,
|
||||||
|
headers?: http.OutgoingHttpHeaders
|
||||||
|
): ifm.RequestInfo {
|
||||||
|
const info: ifm.RequestInfo = <ifm.RequestInfo>{}
|
||||||
|
|
||||||
|
info.parsedUrl = requestUrl
|
||||||
|
const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
|
||||||
|
info.httpModule = usingSsl ? https : http
|
||||||
|
const defaultPort: number = usingSsl ? 443 : 80
|
||||||
|
|
||||||
|
info.options = <http.RequestOptions>{}
|
||||||
|
info.options.host = info.parsedUrl.hostname
|
||||||
|
info.options.port = info.parsedUrl.port
|
||||||
|
? parseInt(info.parsedUrl.port)
|
||||||
|
: defaultPort
|
||||||
|
info.options.path =
|
||||||
|
(info.parsedUrl.pathname || '') + (info.parsedUrl.search || '')
|
||||||
|
info.options.method = method
|
||||||
|
info.options.headers = this._mergeHeaders(headers)
|
||||||
|
if (this.userAgent != null) {
|
||||||
|
info.options.headers['user-agent'] = this.userAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
info.options.agent = this._getAgent(info.parsedUrl)
|
||||||
|
|
||||||
|
// gives handlers an opportunity to participate
|
||||||
|
if (this.handlers) {
|
||||||
|
for (const handler of this.handlers) {
|
||||||
|
handler.prepareRequest(info.options)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info
|
||||||
|
}
|
||||||
|
|
||||||
|
private _mergeHeaders(
|
||||||
|
headers?: http.OutgoingHttpHeaders
|
||||||
|
): http.OutgoingHttpHeaders {
|
||||||
|
if (this.requestOptions && this.requestOptions.headers) {
|
||||||
|
return Object.assign(
|
||||||
|
{},
|
||||||
|
lowercaseKeys(this.requestOptions.headers),
|
||||||
|
lowercaseKeys(headers || {})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lowercaseKeys(headers || {})
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getExistingOrDefaultHeader(
|
||||||
|
additionalHeaders: http.OutgoingHttpHeaders,
|
||||||
|
header: string,
|
||||||
|
_default: string
|
||||||
|
): string | number | string[] {
|
||||||
|
let clientHeader: string | undefined
|
||||||
|
if (this.requestOptions && this.requestOptions.headers) {
|
||||||
|
clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
|
||||||
|
}
|
||||||
|
return additionalHeaders[header] || clientHeader || _default
|
||||||
|
}
|
||||||
|
|
||||||
|
private _getAgent(parsedUrl: URL): http.Agent {
|
||||||
|
let agent
|
||||||
|
const proxyUrl = pm.getProxyUrl(parsedUrl)
|
||||||
|
const useProxy = proxyUrl && proxyUrl.hostname
|
||||||
|
|
||||||
|
if (this._keepAlive && useProxy) {
|
||||||
|
agent = this._proxyAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._keepAlive && !useProxy) {
|
||||||
|
agent = this._agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// if agent is already assigned use that agent.
|
||||||
|
if (agent) {
|
||||||
|
return agent
|
||||||
|
}
|
||||||
|
|
||||||
|
const usingSsl = parsedUrl.protocol === 'https:'
|
||||||
|
let maxSockets = 100
|
||||||
|
if (this.requestOptions) {
|
||||||
|
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
|
||||||
|
}
|
||||||
|
|
||||||
|
// This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
|
||||||
|
if (proxyUrl && proxyUrl.hostname) {
|
||||||
|
const agentOptions = {
|
||||||
|
maxSockets,
|
||||||
|
keepAlive: this._keepAlive,
|
||||||
|
proxy: {
|
||||||
|
...((proxyUrl.username || proxyUrl.password) && {
|
||||||
|
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
|
||||||
|
}),
|
||||||
|
host: proxyUrl.hostname,
|
||||||
|
port: proxyUrl.port
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tunnelAgent: Function
|
||||||
|
const overHttps = proxyUrl.protocol === 'https:'
|
||||||
|
if (usingSsl) {
|
||||||
|
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp
|
||||||
|
} else {
|
||||||
|
tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp
|
||||||
|
}
|
||||||
|
|
||||||
|
agent = tunnelAgent(agentOptions)
|
||||||
|
this._proxyAgent = agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// if reusing agent across request and tunneling agent isn't assigned create a new agent
|
||||||
|
if (this._keepAlive && !agent) {
|
||||||
|
const options = {keepAlive: this._keepAlive, maxSockets}
|
||||||
|
agent = usingSsl ? new https.Agent(options) : new http.Agent(options)
|
||||||
|
this._agent = agent
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not using private agent and tunnel agent isn't setup then use global agent
|
||||||
|
if (!agent) {
|
||||||
|
agent = usingSsl ? https.globalAgent : http.globalAgent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingSsl && this._ignoreSslError) {
|
||||||
|
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
||||||
|
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
||||||
|
// we have to cast it to any and change it directly
|
||||||
|
agent.options = Object.assign(agent.options || {}, {
|
||||||
|
rejectUnauthorized: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return agent
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _performExponentialBackoff(retryNumber: number): Promise<void> {
|
||||||
|
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
|
||||||
|
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
|
||||||
|
return new Promise(resolve => setTimeout(() => resolve(), ms))
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _processResponse<T>(
|
||||||
|
res: HttpClientResponse,
|
||||||
|
options?: ifm.RequestOptions
|
||||||
|
): Promise<ifm.TypedResponse<T>> {
|
||||||
|
return new Promise<ifm.TypedResponse<T>>(async (resolve, reject) => {
|
||||||
|
const statusCode = res.message.statusCode || 0
|
||||||
|
|
||||||
|
const response: ifm.TypedResponse<T> = {
|
||||||
|
statusCode,
|
||||||
|
result: null,
|
||||||
|
headers: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found leads to null obj returned
|
||||||
|
if (statusCode === HttpCodes.NotFound) {
|
||||||
|
resolve(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the result from the body
|
||||||
|
|
||||||
|
function dateTimeDeserializer(key: any, value: any): any {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const a = new Date(value)
|
||||||
|
if (!isNaN(a.valueOf())) {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
let obj: any
|
||||||
|
let contents: string | undefined
|
||||||
|
|
||||||
|
try {
|
||||||
|
contents = await res.readBody()
|
||||||
|
if (contents && contents.length > 0) {
|
||||||
|
if (options && options.deserializeDates) {
|
||||||
|
obj = JSON.parse(contents, dateTimeDeserializer)
|
||||||
|
} else {
|
||||||
|
obj = JSON.parse(contents)
|
||||||
|
}
|
||||||
|
|
||||||
|
response.result = obj
|
||||||
|
}
|
||||||
|
|
||||||
|
response.headers = res.message.headers
|
||||||
|
} catch (err) {
|
||||||
|
// Invalid resource (contents not json); leaving result obj null
|
||||||
|
}
|
||||||
|
|
||||||
|
// note that 3xx redirects are handled by the http layer.
|
||||||
|
if (statusCode > 299) {
|
||||||
|
let msg: string
|
||||||
|
|
||||||
|
// if exception/error in body, attempt to get better error
|
||||||
|
if (obj && obj.message) {
|
||||||
|
msg = obj.message
|
||||||
|
} else if (contents && contents.length > 0) {
|
||||||
|
// it may be the case that the exception is in the body message as string
|
||||||
|
msg = contents
|
||||||
|
} else {
|
||||||
|
msg = `Failed request: (${statusCode})`
|
||||||
|
}
|
||||||
|
|
||||||
|
const err = new HttpClientError(msg, statusCode)
|
||||||
|
err.result = response.result
|
||||||
|
|
||||||
|
reject(err)
|
||||||
|
} else {
|
||||||
|
resolve(response)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const lowercaseKeys = (obj: {[index: string]: any}): any =>
|
||||||
|
Object.keys(obj).reduce((c: any, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
|
|
@ -0,0 +1,91 @@
|
||||||
|
import * as http from 'http'
|
||||||
|
import * as https from 'https'
|
||||||
|
import {HttpClientResponse} from './index'
|
||||||
|
|
||||||
|
export interface HttpClient {
|
||||||
|
options(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
get(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
del(
|
||||||
|
requestUrl: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
post(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
patch(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
put(
|
||||||
|
requestUrl: string,
|
||||||
|
data: string,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
sendStream(
|
||||||
|
verb: string,
|
||||||
|
requestUrl: string,
|
||||||
|
stream: NodeJS.ReadableStream,
|
||||||
|
additionalHeaders?: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
request(
|
||||||
|
verb: string,
|
||||||
|
requestUrl: string,
|
||||||
|
data: string | NodeJS.ReadableStream,
|
||||||
|
headers: http.OutgoingHttpHeaders
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
requestRaw(
|
||||||
|
info: RequestInfo,
|
||||||
|
data: string | NodeJS.ReadableStream
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
requestRawWithCallback(
|
||||||
|
info: RequestInfo,
|
||||||
|
data: string | NodeJS.ReadableStream,
|
||||||
|
onResult: (err?: Error, res?: HttpClientResponse) => void
|
||||||
|
): void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequestHandler {
|
||||||
|
prepareRequest(options: http.RequestOptions): void
|
||||||
|
canHandleAuthentication(response: HttpClientResponse): boolean
|
||||||
|
handleAuthentication(
|
||||||
|
httpClient: HttpClient,
|
||||||
|
requestInfo: RequestInfo,
|
||||||
|
data: string | NodeJS.ReadableStream | null
|
||||||
|
): Promise<HttpClientResponse>
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequestInfo {
|
||||||
|
options: http.RequestOptions
|
||||||
|
parsedUrl: URL
|
||||||
|
httpModule: typeof http | typeof https
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RequestOptions {
|
||||||
|
headers?: http.OutgoingHttpHeaders
|
||||||
|
socketTimeout?: number
|
||||||
|
ignoreSslError?: boolean
|
||||||
|
allowRedirects?: boolean
|
||||||
|
allowRedirectDowngrade?: boolean
|
||||||
|
maxRedirects?: number
|
||||||
|
maxSockets?: number
|
||||||
|
keepAlive?: boolean
|
||||||
|
deserializeDates?: boolean
|
||||||
|
// Allows retries only on Read operations (since writes may not be idempotent)
|
||||||
|
allowRetries?: boolean
|
||||||
|
maxRetries?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TypedResponse<T> {
|
||||||
|
statusCode: number
|
||||||
|
result: T | null
|
||||||
|
headers: http.IncomingHttpHeaders
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
export function getProxyUrl(reqUrl: URL): URL | undefined {
|
||||||
|
const usingSsl = reqUrl.protocol === 'https:'
|
||||||
|
|
||||||
|
if (checkBypass(reqUrl)) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const proxyVar = (() => {
|
||||||
|
if (usingSsl) {
|
||||||
|
return process.env['https_proxy'] || process.env['HTTPS_PROXY']
|
||||||
|
} else {
|
||||||
|
return process.env['http_proxy'] || process.env['HTTP_PROXY']
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (proxyVar) {
|
||||||
|
return new URL(proxyVar)
|
||||||
|
} else {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkBypass(reqUrl: URL): boolean {
|
||||||
|
if (!reqUrl.hostname) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
|
||||||
|
if (!noProxy) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine the request port
|
||||||
|
let reqPort: number | undefined
|
||||||
|
if (reqUrl.port) {
|
||||||
|
reqPort = Number(reqUrl.port)
|
||||||
|
} else if (reqUrl.protocol === 'http:') {
|
||||||
|
reqPort = 80
|
||||||
|
} else if (reqUrl.protocol === 'https:') {
|
||||||
|
reqPort = 443
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format the request hostname and hostname with port
|
||||||
|
const upperReqHosts = [reqUrl.hostname.toUpperCase()]
|
||||||
|
if (typeof reqPort === 'number') {
|
||||||
|
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare request host against noproxy
|
||||||
|
for (const upperNoProxyItem of noProxy
|
||||||
|
.split(',')
|
||||||
|
.map(x => x.trim().toUpperCase())
|
||||||
|
.filter(x => x)) {
|
||||||
|
if (upperReqHosts.some(x => x === upperNoProxyItem)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./lib",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"./src"
|
||||||
|
]
|
||||||
|
}
|
|
@ -1,5 +1,13 @@
|
||||||
# @actions/tool-cache Releases
|
# @actions/tool-cache Releases
|
||||||
|
|
||||||
|
### 2.0.1
|
||||||
|
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||||
|
|
||||||
|
### 2.0.0
|
||||||
|
- Update to v2.0.0 of `@actions/http-client`
|
||||||
|
- The type of the `headers` parameter in the exported function `downloadTool` has been narrowed from `{ [header: string]: any }` to `{ [header: string]: number | string | string[] | undefined; }` (that is, `http.OutgoingHttpHeaders`).
|
||||||
|
This is strictly a compile-time change for TypeScript consumers. Previous attempts to use a header value of a type other than those now accepted would have resulted in an error at run time.
|
||||||
|
|
||||||
### 1.7.2
|
### 1.7.2
|
||||||
- Update `lockfileVersion` to `v2` in `package-lock.json [#1025](https://github.com/actions/toolkit/pull/1025)
|
- Update `lockfileVersion` to `v2` in `package-lock.json [#1025](https://github.com/actions/toolkit/pull/1025)
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/tool-cache",
|
"name": "@actions/tool-cache",
|
||||||
"version": "1.7.2",
|
"version": "2.0.1",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@actions/tool-cache",
|
"name": "@actions/tool-cache",
|
||||||
"version": "1.7.2",
|
"version": "2.0.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^1.0.8",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.1.1",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^6.1.0",
|
"semver": "^6.1.0",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
|
@ -24,30 +24,41 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/core": {
|
"node_modules/@actions/core": {
|
||||||
"version": "1.2.6",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@actions/core/node_modules/@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"dependencies": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/exec": {
|
"node_modules/@actions/exec": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/http-client": {
|
"node_modules/@actions/http-client": {
|
||||||
"version": "1.0.8",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@actions/io": {
|
"node_modules/@actions/io": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||||
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
|
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||||
},
|
},
|
||||||
"node_modules/@types/nock": {
|
"node_modules/@types/nock": {
|
||||||
"version": "10.0.3",
|
"version": "10.0.3",
|
||||||
|
@ -280,30 +291,43 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": {
|
"@actions/core": {
|
||||||
"version": "1.2.6",
|
"version": "1.8.0",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
|
||||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
|
||||||
|
"requires": {
|
||||||
|
"@actions/http-client": "^1.0.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@actions/http-client": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||||
|
"requires": {
|
||||||
|
"tunnel": "0.0.6"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"@actions/exec": {
|
"@actions/exec": {
|
||||||
"version": "1.0.3",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||||
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
|
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@actions/io": "^1.0.1"
|
"@actions/io": "^1.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/http-client": {
|
"@actions/http-client": {
|
||||||
"version": "1.0.8",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||||
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
|
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tunnel": "0.0.6"
|
"tunnel": "^0.0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@actions/io": {
|
"@actions/io": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||||
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
|
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||||
},
|
},
|
||||||
"@types/nock": {
|
"@types/nock": {
|
||||||
"version": "10.0.3",
|
"version": "10.0.3",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@actions/tool-cache",
|
"name": "@actions/tool-cache",
|
||||||
"version": "1.7.2",
|
"version": "2.0.1",
|
||||||
"description": "Actions tool-cache lib",
|
"description": "Actions tool-cache lib",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"github",
|
"github",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/core": "^1.2.6",
|
"@actions/core": "^1.2.6",
|
||||||
"@actions/exec": "^1.0.0",
|
"@actions/exec": "^1.0.0",
|
||||||
"@actions/http-client": "^1.0.8",
|
"@actions/http-client": "^2.0.1",
|
||||||
"@actions/io": "^1.1.1",
|
"@actions/io": "^1.1.1",
|
||||||
"semver": "^6.1.0",
|
"semver": "^6.1.0",
|
||||||
"uuid": "^3.3.2"
|
"uuid": "^3.3.2"
|
||||||
|
|
|
@ -8,12 +8,12 @@ import * as httpm from '@actions/http-client'
|
||||||
import * as semver from 'semver'
|
import * as semver from 'semver'
|
||||||
import * as stream from 'stream'
|
import * as stream from 'stream'
|
||||||
import * as util from 'util'
|
import * as util from 'util'
|
||||||
|
import {ok} from 'assert'
|
||||||
|
import {OutgoingHttpHeaders} from 'http'
|
||||||
import uuidV4 from 'uuid/v4'
|
import uuidV4 from 'uuid/v4'
|
||||||
import {exec} from '@actions/exec/lib/exec'
|
import {exec} from '@actions/exec/lib/exec'
|
||||||
import {ExecOptions} from '@actions/exec/lib/interfaces'
|
import {ExecOptions} from '@actions/exec/lib/interfaces'
|
||||||
import {ok} from 'assert'
|
|
||||||
import {RetryHelper} from './retry-helper'
|
import {RetryHelper} from './retry-helper'
|
||||||
import {IHeaders} from '@actions/http-client/interfaces'
|
|
||||||
|
|
||||||
export class HTTPError extends Error {
|
export class HTTPError extends Error {
|
||||||
constructor(readonly httpStatusCode: number | undefined) {
|
constructor(readonly httpStatusCode: number | undefined) {
|
||||||
|
@ -39,7 +39,7 @@ export async function downloadTool(
|
||||||
url: string,
|
url: string,
|
||||||
dest?: string,
|
dest?: string,
|
||||||
auth?: string,
|
auth?: string,
|
||||||
headers?: IHeaders
|
headers?: OutgoingHttpHeaders
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
dest = dest || path.join(_getTempDirectory(), uuidV4())
|
dest = dest || path.join(_getTempDirectory(), uuidV4())
|
||||||
await io.mkdirP(path.dirname(dest))
|
await io.mkdirP(path.dirname(dest))
|
||||||
|
@ -82,7 +82,7 @@ async function downloadToolAttempt(
|
||||||
url: string,
|
url: string,
|
||||||
dest: string,
|
dest: string,
|
||||||
auth?: string,
|
auth?: string,
|
||||||
headers?: IHeaders
|
headers?: OutgoingHttpHeaders
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
if (fs.existsSync(dest)) {
|
if (fs.existsSync(dest)) {
|
||||||
throw new Error(`Destination file path ${dest} already exists`)
|
throw new Error(`Destination file path ${dest} already exists`)
|
||||||
|
@ -596,7 +596,7 @@ export async function getManifestFromRepo(
|
||||||
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`
|
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`
|
||||||
|
|
||||||
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache')
|
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache')
|
||||||
const headers: IHeaders = {}
|
const headers: OutgoingHttpHeaders = {}
|
||||||
if (auth) {
|
if (auth) {
|
||||||
core.debug('set auth')
|
core.debug('set auth')
|
||||||
headers.authorization = auth
|
headers.authorization = auth
|
||||||
|
|
|
@ -9,5 +9,5 @@ if [[ -z "$name" ]]; then
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
lerna create @actions/$name
|
npx lerna create @actions/$name
|
||||||
cp packages/toolkit/tsconfig.json packages/$name/tsconfig.json
|
cp packages/core/tsconfig.json packages/$name/tsconfig.json
|
Loading…
Reference in New Issue