Compare commits

..

28 Commits

Author SHA1 Message Date
Shubham Tiwari 6ccc235c6d Update RELEASES.md 2022-03-25 11:52:13 +05:30
Shubham Tiwari 9d24b58739 Revert "reverting version update"
This reverts commit af84eba61e.
2022-03-25 11:51:01 +05:30
Shubham Tiwari af84eba61e reverting version update 2022-03-25 10:59:17 +05:30
Shubham Tiwari 385e1ed722 Merge branch 'enablingForGHES' of github.com:actions/toolkit into enablingForGHES 2022-03-25 00:51:58 +05:30
Shubham Tiwari 336e5e4ce8 remove extra spaces 2022-03-25 00:51:25 +05:30
Shubham Tiwari 299d3dd2ec Update RELEASES.md 2022-03-25 00:40:46 +05:30
Shubham Tiwari 8251d0edc0 push to start CI 2022-03-25 00:37:41 +05:30
Shubham Tiwari 2a53a08370 linting 2022-03-25 00:26:40 +05:30
Shubham Tiwari 6664f1646a linting 2022-03-25 00:25:03 +05:30
Shubham Tiwari ec8e458bdc merge conflicts 2022-03-25 00:06:54 +05:30
Shubham Tiwari 2487627b27 review comments 2022-03-25 00:02:17 +05:30
Shubham Tiwari 71b35793b1 Update packages/cache/src/internal/cacheUtils.ts
Co-authored-by: Brian Cristante <33549821+brcrista@users.noreply.github.com>
2022-03-24 17:17:35 +05:30
Shubham Tiwari 4cb65fcdd9 update name to actions service 2022-03-24 13:36:58 +05:30
Shubham Tiwari 77a6cb490e lint 2022-03-24 12:37:32 +05:30
Shubham Tiwari a54ac04ab9 linting 2022-03-24 12:10:14 +05:30
Shubham Tiwari a4539ea36c lint 2022-03-24 11:55:57 +05:30
Shubham Tiwari 4e40c6dfc0 t pMerge branch 'enablingForGHES' of github.com:actions/toolkit into enablingForGHES 2022-03-24 11:46:21 +05:30
Shubham Tiwari 31035de89b Lint errors 2022-03-24 11:45:50 +05:30
Shubham Tiwari bc0e8f3536 Update RELEASES.md 2022-03-24 11:28:55 +05:30
Shubham Tiwari ba1126827e added test case 2022-03-24 11:26:30 +05:30
Shubham Tiwari a1a0cc7ebf Updated release 2022-03-24 09:01:37 +05:30
Shubham Tiwari d8025e51e1 Function rename 2022-03-24 09:00:25 +05:30
Shubham Tiwari 8fe3522c0b changed function name 2022-03-23 16:46:49 +05:30
Shubham Tiwari 05f407f4ff CI fix 2022-03-23 02:53:18 +05:30
Shubham Tiwari 429adbf862 Fix CI 2022-03-23 02:44:07 +05:30
Shubham Tiwari 3c38dcfe1f added ACTIONS_CACHE_URL in fixtures 2022-03-23 02:40:43 +05:30
Shubham Tiwari d87ca655a9 enablingForGHES 2022-03-23 02:25:34 +05:30
Shubham Tiwari 717b6fd18b Added support to check if Artifact cache service is enabled or not. 2022-03-23 02:24:43 +05:30
41 changed files with 10148 additions and 29941 deletions
+3107 -16397
View File
File diff suppressed because it is too large Load Diff
+1 -33
View File
@@ -1,6 +1,5 @@
import {retry, retryTypedResponse} from '../src/internal/requestUtils'
import {retry} from '../src/internal/requestUtils'
import {HttpClientError} from '@actions/http-client'
import * as requestUtils from '../src/internal/requestUtils'
interface ITestResponse {
statusCode: number
@@ -146,34 +145,3 @@ test('retry converts errors to response object', async () => {
null
)
})
test('retryTypedResponse gives an error with error message', async () => {
const httpClientError = new HttpClientError(
'The cache filesize must be between 0 and 10 * 1024 * 1024 bytes',
400
)
jest.spyOn(requestUtils, 'retry').mockReturnValue(
new Promise(resolve => {
resolve(httpClientError)
})
)
try {
await retryTypedResponse<string>(
'reserveCache',
async () =>
new Promise(resolve => {
resolve({
statusCode: 400,
result: '',
headers: {},
error: httpClientError
})
})
)
} catch (error) {
expect(error).toHaveProperty(
'message',
'The cache filesize must be between 0 and 10 * 1024 * 1024 bytes'
)
}
})
+13 -117
View File
@@ -5,12 +5,6 @@ import * as cacheHttpClient from '../src/internal/cacheHttpClient'
import * as cacheUtils from '../src/internal/cacheUtils'
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
import * as tar from '../src/internal/tar'
import {ITypedResponse} from '@actions/http-client/interfaces'
import {
ReserveCacheResponse,
ITypedResponseWithError
} from '../src/internal/contracts'
import {HttpClientError} from '@actions/http-client'
jest.mock('../src/internal/cacheHttpClient')
jest.mock('../src/internal/cacheUtils')
@@ -22,13 +16,16 @@ beforeAll(() => {
jest.spyOn(core, 'info').mockImplementation(() => {})
jest.spyOn(core, 'warning').mockImplementation(() => {})
jest.spyOn(core, 'error').mockImplementation(() => {})
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
return actualUtils.getCacheFileName(cm)
})
jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async filePaths => {
return filePaths.map(x => path.resolve(x))
})
jest.spyOn(cacheUtils, 'createTempDirectory').mockImplementation(async () => {
return Promise.resolve('/foo/bar')
})
@@ -73,98 +70,6 @@ test('save with large cache outputs should fail', async () => {
expect(getCompressionMock).toHaveBeenCalledTimes(1)
})
test('save with large cache outputs should fail in GHES with error message', async () => {
const filePath = 'node_modules'
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
const cachePaths = [path.resolve(filePath)]
const createTarMock = jest.spyOn(tar, 'createTar')
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
jest
.spyOn(cacheUtils, 'getArchiveFileSizeInBytes')
.mockReturnValueOnce(cacheSize)
const compression = CompressionMethod.Gzip
const getCompressionMock = jest
.spyOn(cacheUtils, 'getCompressionMethod')
.mockReturnValueOnce(Promise.resolve(compression))
jest.spyOn(cacheUtils, 'isGhes').mockReturnValueOnce(true)
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponseWithError<ReserveCacheResponse> = {
statusCode: 400,
result: null,
headers: {},
error: new HttpClientError(
'The cache filesize must be between 0 and 1073741824 bytes',
400
)
}
return response
})
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
'The cache filesize must be between 0 and 1073741824 bytes'
)
const archiveFolder = '/foo/bar'
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
)
expect(getCompressionMock).toHaveBeenCalledTimes(1)
})
test('save with large cache outputs should fail in GHES without error message', async () => {
const filePath = 'node_modules'
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
const cachePaths = [path.resolve(filePath)]
const createTarMock = jest.spyOn(tar, 'createTar')
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
jest
.spyOn(cacheUtils, 'getArchiveFileSizeInBytes')
.mockReturnValueOnce(cacheSize)
const compression = CompressionMethod.Gzip
const getCompressionMock = jest
.spyOn(cacheUtils, 'getCompressionMethod')
.mockReturnValueOnce(Promise.resolve(compression))
jest.spyOn(cacheUtils, 'isGhes').mockReturnValueOnce(true)
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponseWithError<ReserveCacheResponse> = {
statusCode: 400,
result: null,
headers: {}
}
return response
})
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
'Cache size of ~11264 MB (11811160064 B) is over the data cap limit, not saving cache.'
)
const archiveFolder = '/foo/bar'
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
)
expect(getCompressionMock).toHaveBeenCalledTimes(1)
})
test('save with reserve cache failure should fail', async () => {
const paths = ['node_modules']
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
@@ -172,12 +77,7 @@ test('save with reserve cache failure should fail', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: null,
headers: {}
}
return response
return -1
})
const createTarMock = jest.spyOn(tar, 'createTar')
@@ -194,7 +94,7 @@ test('save with reserve cache failure should fail', async () => {
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, paths, {
compressionMethod: compression
})
expect(createTarMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledTimes(0)
expect(saveCacheMock).toHaveBeenCalledTimes(0)
expect(getCompressionMock).toHaveBeenCalledTimes(1)
})
@@ -208,12 +108,7 @@ test('save with server error should fail', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: {cacheId},
headers: {}
}
return response
return cacheId
})
const createTarMock = jest.spyOn(tar, 'createTar')
@@ -235,14 +130,17 @@ test('save with server error should fail', async () => {
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
compressionMethod: compression
})
const archiveFolder = '/foo/bar'
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
expect(createTarMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
)
expect(saveCacheMock).toHaveBeenCalledTimes(1)
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
expect(getCompressionMock).toHaveBeenCalledTimes(1)
@@ -257,12 +155,7 @@ test('save with valid inputs uploads a cache', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: {cacheId},
headers: {}
}
return response
return cacheId
})
const createTarMock = jest.spyOn(tar, 'createTar')
@@ -278,14 +171,17 @@ test('save with valid inputs uploads a cache', async () => {
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
compressionMethod: compression
})
const archiveFolder = '/foo/bar'
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
expect(createTarMock).toHaveBeenCalledTimes(1)
expect(createTarMock).toHaveBeenCalledWith(
archiveFolder,
cachePaths,
compression
)
expect(saveCacheMock).toHaveBeenCalledTimes(1)
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
expect(getCompressionMock).toHaveBeenCalledTimes(1)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/cache",
"version": "2.0.2",
"version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/cache",
"version": "2.0.2",
"version": "2.0.0",
"preview": true,
"description": "Actions cache lib",
"keywords": [
+13 -29
View File
@@ -152,7 +152,17 @@ export async function saveCache(
checkKey(key)
const compressionMethod = await utils.getCompressionMethod()
let cacheId = null
core.debug('Reserving Cache')
const cacheId = await cacheHttpClient.reserveCache(key, paths, {
compressionMethod
})
if (cacheId === -1) {
throw new ReserveCacheError(
`Unable to reserve cache with key ${key}, another job may be creating this cache.`
)
}
core.debug(`Cache ID: ${cacheId}`)
const cachePaths = await utils.resolvePaths(paths)
core.debug('Cache Paths:')
@@ -171,12 +181,11 @@ export async function saveCache(
if (core.isDebug()) {
await listTar(archivePath, compressionMethod)
}
const fileSizeLimit = 10 * 1024 * 1024 * 1024 // 10GB per repo limit
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath)
core.debug(`File Size: ${archiveFileSize}`)
// For GHES, this check will take place in ReserveCache API with enterprise file size limit
if (archiveFileSize > fileSizeLimit && !utils.isGhes()) {
if (archiveFileSize > fileSizeLimit) {
throw new Error(
`Cache size of ~${Math.round(
archiveFileSize / (1024 * 1024)
@@ -184,31 +193,6 @@ export async function saveCache(
)
}
core.debug('Reserving Cache')
const reserveCacheResponse = await cacheHttpClient.reserveCache(
key,
paths,
{
compressionMethod,
cacheSize: archiveFileSize
}
)
if (reserveCacheResponse?.result?.cacheId) {
cacheId = reserveCacheResponse?.result?.cacheId
} else if (reserveCacheResponse?.statusCode === 400) {
throw new Error(
reserveCacheResponse?.error?.message ??
`Cache size of ~${Math.round(
archiveFileSize / (1024 * 1024)
)} MB (${archiveFileSize} B) is over the data cap limit, not saving cache.`
)
} else {
throw new ReserveCacheError(
`Unable to reserve cache with key ${key}, another job may be creating this cache. More details: ${reserveCacheResponse?.error?.message}`
)
}
core.debug(`Saving Cache (ID: ${cacheId})`)
await cacheHttpClient.saveCache(cacheId, archivePath, options)
} finally {
+4 -6
View File
@@ -13,8 +13,7 @@ import {
InternalCacheOptions,
CommitCacheRequest,
ReserveCacheRequest,
ReserveCacheResponse,
ITypedResponseWithError
ReserveCacheResponse
} from './contracts'
import {downloadCacheHttpClient, downloadCacheStorageSDK} from './downloadUtils'
import {
@@ -144,14 +143,13 @@ export async function reserveCache(
key: string,
paths: string[],
options?: InternalCacheOptions
): Promise<ITypedResponseWithError<ReserveCacheResponse>> {
): Promise<number> {
const httpClient = createHttpClient()
const version = getCacheVersion(paths, options?.compressionMethod)
const reserveCacheRequest: ReserveCacheRequest = {
key,
version,
cacheSize: options?.cacheSize
version
}
const response = await retryTypedResponse('reserveCache', async () =>
httpClient.postJson<ReserveCacheResponse>(
@@ -159,7 +157,7 @@ export async function reserveCache(
reserveCacheRequest
)
)
return response
return response?.result?.cacheId ?? -1
}
function getContentRange(start: number, end: number): string {
-7
View File
@@ -123,10 +123,3 @@ export function assertDefined<T>(name: string, value?: T): T {
return value
}
export function isGhes(): boolean {
const ghUrl = new URL(
process.env['GITHUB_SERVER_URL'] || 'https://github.com'
)
return ghUrl.hostname.toUpperCase() !== 'GITHUB.COM'
}
-8
View File
@@ -1,10 +1,4 @@
import {CompressionMethod} from './constants'
import {ITypedResponse} from '@actions/http-client/interfaces'
import {HttpClientError} from '@actions/http-client'
export interface ITypedResponseWithError<T> extends ITypedResponse<T> {
error?: HttpClientError
}
export interface ArtifactCacheEntry {
cacheKey?: string
@@ -20,7 +14,6 @@ export interface CommitCacheRequest {
export interface ReserveCacheRequest {
key: string
version?: string
cacheSize?: number
}
export interface ReserveCacheResponse {
@@ -29,5 +22,4 @@ export interface ReserveCacheResponse {
export interface InternalCacheOptions {
compressionMethod?: CompressionMethod
cacheSize?: number
}
+8 -7
View File
@@ -1,8 +1,10 @@
import * as core from '@actions/core'
import {HttpCodes, HttpClientError} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {
IHttpClientResponse,
ITypedResponse
} from '@actions/http-client/interfaces'
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
import {ITypedResponseWithError} from './contracts'
export function isSuccessStatusCode(statusCode?: number): boolean {
if (!statusCode) {
@@ -92,14 +94,14 @@ export async function retry<T>(
export async function retryTypedResponse<T>(
name: string,
method: () => Promise<ITypedResponseWithError<T>>,
method: () => Promise<ITypedResponse<T>>,
maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay
): Promise<ITypedResponseWithError<T>> {
): Promise<ITypedResponse<T>> {
return await retry(
name,
method,
(response: ITypedResponseWithError<T>) => response.statusCode,
(response: ITypedResponse<T>) => response.statusCode,
maxAttempts,
delay,
// If the error object contains the statusCode property, extract it and return
@@ -109,8 +111,7 @@ export async function retryTypedResponse<T>(
return {
statusCode: error.statusCode,
result: null,
headers: {},
error
headers: {}
}
} else {
return undefined
-3
View File
@@ -1,8 +1,5 @@
# @actions/core Releases
### 1.7.0
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
### 1.6.0
- [Added OIDC Client function `getIDToken`](https://github.com/actions/toolkit/pull/919)
- [Added `file` parameter to `AnnotationProperties`](https://github.com/actions/toolkit/pull/896)
@@ -1,277 +0,0 @@
import * as fs from 'fs'
import * as os from 'os'
import path from 'path'
import {markdownSummary, SUMMARY_ENV_VAR} from '../src/markdown-summary'
const testFilePath = path.join(__dirname, 'test', 'test-summary.md')
async function assertSummary(expected: string): Promise<void> {
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
expect(file).toEqual(expected)
}
const fixtures = {
text: 'hello world 🌎',
code: `func fork() {
for {
go fork()
}
}`,
list: ['foo', 'bar', 'baz', '💣'],
table: [
[
{
data: 'foo',
header: true
},
{
data: 'bar',
header: true
},
{
data: 'baz',
header: true
},
{
data: 'tall',
rowspan: '3'
}
],
['one', 'two', 'three'],
[
{
data: 'wide',
colspan: '3'
}
]
],
details: {
label: 'open me',
content: '🎉 surprise'
},
img: {
src: 'https://github.com/actions.png',
alt: 'actions logo',
options: {
width: '32',
height: '32'
}
},
quote: {
text: 'Where the world builds software',
cite: 'https://github.com/about'
},
link: {
text: 'GitHub',
href: 'https://github.com/'
}
}
describe('@actions/core/src/markdown-summary', () => {
beforeEach(async () => {
process.env[SUMMARY_ENV_VAR] = testFilePath
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
markdownSummary.emptyBuffer()
})
afterAll(async () => {
await fs.promises.unlink(testFilePath)
})
it('throws if summary env var is undefined', async () => {
process.env[SUMMARY_ENV_VAR] = undefined
const write = markdownSummary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow()
})
it('throws if summary file does not exist', async () => {
await fs.promises.unlink(testFilePath)
const write = markdownSummary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow()
})
it('appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write()
await assertSummary(`# ${fixtures.text}`)
})
it('overwrites text to summary file', async () => {
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write({overwrite: true})
await assertSummary(fixtures.text)
})
it('appends text with EOL to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text, true).write()
await assertSummary(`# ${fixtures.text}${os.EOL}`)
})
it('chains appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary
.addRaw(fixtures.text)
.addRaw(fixtures.text)
.addRaw(fixtures.text)
.write()
await assertSummary([fixtures.text, fixtures.text, fixtures.text].join(''))
})
it('empties buffer after write', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write()
await assertSummary(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(true)
})
it('returns summary buffer as string', () => {
markdownSummary.addRaw(fixtures.text)
expect(markdownSummary.stringify()).toEqual(fixtures.text)
})
it('return correct values for isEmptyBuffer', () => {
markdownSummary.addRaw(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(false)
markdownSummary.emptyBuffer()
expect(markdownSummary.isEmptyBuffer()).toBe(true)
})
it('clears a buffer and summary file', async () => {
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
await markdownSummary.clear()
await assertSummary('')
expect(markdownSummary.isEmptyBuffer()).toBe(true)
})
it('adds EOL', async () => {
await markdownSummary
.addRaw(fixtures.text)
.addEOL()
.write()
await assertSummary(fixtures.text + os.EOL)
})
it('adds a code block without language', async () => {
await markdownSummary.addCodeBlock(fixtures.code).write()
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected)
})
it('adds a code block with a language', async () => {
await markdownSummary.addCodeBlock(fixtures.code, 'go').write()
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected)
})
it('adds an unordered list', async () => {
await markdownSummary.addList(fixtures.list).write()
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
await assertSummary(expected)
})
it('adds an ordered list', async () => {
await markdownSummary.addList(fixtures.list, true).write()
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
await assertSummary(expected)
})
it('adds a table', async () => {
await markdownSummary.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}`
await assertSummary(expected)
})
it('adds a details element', async () => {
await markdownSummary
.addDetails(fixtures.details.label, fixtures.details.content)
.write()
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
await assertSummary(expected)
})
it('adds an image with alt text', async () => {
await markdownSummary.addImage(fixtures.img.src, fixtures.img.alt).write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
await assertSummary(expected)
})
it('adds an image with custom dimensions', async () => {
await markdownSummary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
await assertSummary(expected)
})
it('adds an image with custom dimensions', async () => {
await markdownSummary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
await assertSummary(expected)
})
it('adds headings h1...h6', async () => {
for (const i of [1, 2, 3, 4, 5, 6]) {
markdownSummary.addHeading('heading', i)
}
await markdownSummary.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}`
await assertSummary(expected)
})
it('adds h1 if heading level not specified', async () => {
await markdownSummary.addHeading('heading').write()
const expected = `<h1>heading</h1>${os.EOL}`
await assertSummary(expected)
})
it('uses h1 if heading level is garbage or out of range', async () => {
await markdownSummary
.addHeading('heading', 'foobar')
.addHeading('heading', 1337)
.addHeading('heading', -1)
.addHeading('heading', Infinity)
.write()
const expected = `<h1>heading</h1>${os.EOL}<h1>heading</h1>${os.EOL}<h1>heading</h1>${os.EOL}<h1>heading</h1>${os.EOL}`
await assertSummary(expected)
})
it('adds a separator', async () => {
await markdownSummary.addSeparator().write()
const expected = `<hr>${os.EOL}`
await assertSummary(expected)
})
it('adds a break', async () => {
await markdownSummary.addBreak().write()
const expected = `<br>${os.EOL}`
await assertSummary(expected)
})
it('adds a quote', async () => {
await markdownSummary.addQuote(fixtures.quote.text).write()
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected)
})
it('adds a quote with citation', async () => {
await markdownSummary
.addQuote(fixtures.quote.text, fixtures.quote.cite)
.write()
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected)
})
it('adds a link with href', async () => {
await markdownSummary
.addLink(fixtures.link.text, fixtures.link.href)
.write()
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
await assertSummary(expected)
})
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "1.7.0",
"version": "1.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "1.7.0",
"version": "1.6.0",
"description": "Actions core lib",
"keywords": [
"github",
-5
View File
@@ -359,8 +359,3 @@ export function getState(name: string): string {
export async function getIDToken(aud?: string): Promise<string> {
return await OidcClient.getIDToken(aud)
}
/**
* Markdown summary exports
*/
export {markdownSummary} from './markdown-summary'
-362
View File
@@ -1,362 +0,0 @@
import {EOL} from 'os'
import {constants, promises} from 'fs'
const {access, appendFile, writeFile} = promises
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
export const SUMMARY_DOCS_URL =
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary'
export type SummaryTableRow = (SummaryTableCell | string)[]
export interface SummaryTableCell {
/**
* Cell content
*/
data: string
/**
* Render cell as header
* (optional) default: false
*/
header?: boolean
/**
* Number of columns the cell extends
* (optional) default: '1'
*/
colspan?: string
/**
* Number of rows the cell extends
* (optional) default: '1'
*/
rowspan?: string
}
export interface SummaryImageOptions {
/**
* The width of the image in pixels. Must be an integer without a unit.
* (optional)
*/
width?: string
/**
* The height of the image in pixels. Must be an integer without a unit.
* (optional)
*/
height?: string
}
export interface SummaryWriteOptions {
/**
* Replace all existing content in summary file with buffer contents
* (optional) default: false
*/
overwrite?: boolean
}
class MarkdownSummary {
private _buffer: string
private _filePath?: string
constructor() {
this._buffer = ''
}
/**
* Finds the summary file path from the environment, rejects if env var is not found or file does not exist
* Also checks r/w permissions.
*
* @returns step summary file path
*/
private async filePath(): Promise<string> {
if (this._filePath) {
return this._filePath
}
const pathFromEnv = process.env[SUMMARY_ENV_VAR]
if (!pathFromEnv) {
throw new Error(
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`
)
}
try {
await access(pathFromEnv, constants.R_OK | constants.W_OK)
} catch {
throw new Error(
`Unable to access summary file: '${pathFromEnv}'. Check if the file has correct read/write permissions.`
)
}
this._filePath = pathFromEnv
return this._filePath
}
/**
* Wraps content in an HTML tag, adding any HTML attributes
*
* @param {string} tag HTML tag to wrap
* @param {string | null} content content within the tag
* @param {[attribute: string]: string} attrs key-value list of HTML attributes to add
*
* @returns {string} content wrapped in HTML element
*/
private wrap(
tag: string,
content: string | null,
attrs: {[attribute: string]: string} = {}
): string {
const htmlAttrs = Object.entries(attrs)
.map(([key, value]) => ` ${key}="${value}"`)
.join('')
if (!content) {
return `<${tag}${htmlAttrs}>`
}
return `<${tag}${htmlAttrs}>${content}</${tag}>`
}
/**
* Writes text in the buffer to the summary buffer file and empties buffer. Will append by default.
*
* @param {SummaryWriteOptions} [options] (optional) options for write operation
*
* @returns {Promise<MarkdownSummary>} markdown summary instance
*/
async write(options?: SummaryWriteOptions): Promise<MarkdownSummary> {
const overwrite = !!options?.overwrite
const filePath = await this.filePath()
const writeFunc = overwrite ? writeFile : appendFile
await writeFunc(filePath, this._buffer, {encoding: 'utf8'})
return this.emptyBuffer()
}
/**
* Clears the summary buffer and wipes the summary file
*
* @returns {MarkdownSummary} markdown summary instance
*/
async clear(): Promise<MarkdownSummary> {
return this.emptyBuffer().write({overwrite: true})
}
/**
* Returns the current summary buffer as a string
*
* @returns {string} string of summary buffer
*/
stringify(): string {
return this._buffer
}
/**
* If the summary buffer is empty
*
* @returns {boolen} true if the buffer is empty
*/
isEmptyBuffer(): boolean {
return this._buffer.length === 0
}
/**
* Resets the summary buffer without writing to summary file
*
* @returns {MarkdownSummary} markdown summary instance
*/
emptyBuffer(): MarkdownSummary {
this._buffer = ''
return this
}
/**
* Adds raw text to the summary buffer
*
* @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
*
* @returns {MarkdownSummary} markdown summary instance
*/
addRaw(text: string, addEOL = false): MarkdownSummary {
this._buffer += text
return addEOL ? this.addEOL() : this
}
/**
* Adds the operating system-specific end-of-line marker to the buffer
*
* @returns {MarkdownSummary} markdown summary instance
*/
addEOL(): MarkdownSummary {
return this.addRaw(EOL)
}
/**
* Adds an HTML codeblock to the summary buffer
*
* @param {string} code content to render within fenced code block
* @param {string} lang (optional) language to syntax highlight code
*
* @returns {MarkdownSummary} markdown summary instance
*/
addCodeBlock(code: string, lang?: string): MarkdownSummary {
const attrs = {
...(lang && {lang})
}
const element = this.wrap('pre', this.wrap('code', code), attrs)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML list to the summary buffer
*
* @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
*
* @returns {MarkdownSummary} markdown summary instance
*/
addList(items: string[], ordered = false): MarkdownSummary {
const tag = ordered ? 'ol' : 'ul'
const listItems = items.map(item => this.wrap('li', item)).join('')
const element = this.wrap(tag, listItems)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML table to the summary buffer
*
* @param {SummaryTableCell[]} rows table rows
*
* @returns {MarkdownSummary} markdown summary instance
*/
addTable(rows: SummaryTableRow[]): MarkdownSummary {
const tableBody = rows
.map(row => {
const cells = row
.map(cell => {
if (typeof cell === 'string') {
return this.wrap('td', cell)
}
const {header, data, colspan, rowspan} = cell
const tag = header ? 'th' : 'td'
const attrs = {
...(colspan && {colspan}),
...(rowspan && {rowspan})
}
return this.wrap(tag, data, attrs)
})
.join('')
return this.wrap('tr', cells)
})
.join('')
const element = this.wrap('table', tableBody)
return this.addRaw(element).addEOL()
}
/**
* Adds a collapsable HTML details element to the summary buffer
*
* @param {string} label text for the closed state
* @param {string} content collapsable content
*
* @returns {MarkdownSummary} markdown summary instance
*/
addDetails(label: string, content: string): MarkdownSummary {
const element = this.wrap('details', this.wrap('summary', label) + content)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML image tag to the summary buffer
*
* @param {string} src path to the image you to embed
* @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes
*
* @returns {MarkdownSummary} markdown summary instance
*/
addImage(
src: string,
alt: string,
options?: SummaryImageOptions
): MarkdownSummary {
const {width, height} = options || {}
const attrs = {
...(width && {width}),
...(height && {height})
}
const element = this.wrap('img', null, {src, alt, ...attrs})
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML section heading element
*
* @param {string} text heading text
* @param {number | string} [level=1] (optional) the heading level, default: 1
*
* @returns {MarkdownSummary} markdown summary instance
*/
addHeading(text: string, level?: number | string): MarkdownSummary {
const tag = `h${level}`
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
? tag
: 'h1'
const element = this.wrap(allowedTag, text)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML thematic break (<hr>) to the summary buffer
*
* @returns {MarkdownSummary} markdown summary instance
*/
addSeparator(): MarkdownSummary {
const element = this.wrap('hr', null)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML line break (<br>) to the summary buffer
*
* @returns {MarkdownSummary} markdown summary instance
*/
addBreak(): MarkdownSummary {
const element = this.wrap('br', null)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML blockquote to the summary buffer
*
* @param {string} text quote text
* @param {string} cite (optional) citation url
*
* @returns {MarkdownSummary} markdown summary instance
*/
addQuote(text: string, cite?: string): MarkdownSummary {
const attrs = {
...(cite && {cite})
}
const element = this.wrap('blockquote', text, attrs)
return this.addRaw(element).addEOL()
}
/**
* Adds an HTML anchor tag to the summary buffer
*
* @param {string} text link text/content
* @param {string} href hyperlink
*
* @returns {MarkdownSummary} markdown summary instance
*/
addLink(text: string, href: string): MarkdownSummary {
const element = this.wrap('a', text, {href})
return this.addRaw(element).addEOL()
}
}
// singleton export
export const markdownSummary = new MarkdownSummary()
-2
View File
@@ -1,7 +1,5 @@
# @actions/github Releases
### 5.0.1
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
### 5.0.0
- [Update @actions/github to include latest octokit definitions](https://github.com/actions/toolkit/pull/783)
- [Add urls to context](https://github.com/actions/toolkit/pull/794)
+6956 -114
View File
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/github",
"version": "5.0.1",
"version": "5.0.0",
"description": "Actions github lib",
"keywords": [
"github",
@@ -39,9 +39,9 @@
},
"dependencies": {
"@actions/http-client": "^1.0.11",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
"@octokit/core": "^3.4.0",
"@octokit/plugin-paginate-rest": "^2.13.3",
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
},
"devDependencies": {
"proxy": "^1.0.2"
-3
View File
@@ -1,8 +1,5 @@
# @actions/glob Releases
### 0.3.0
- Added a `verbose` option to HashFiles [#1052](https://github.com/actions/toolkit/pull/1052/files)
### 0.2.1
- Update `lockfileVersion` to `v2` in `package-lock.json [#1023](https://github.com/actions/toolkit/pull/1023)
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.3.0",
"version": "0.2.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.3.0",
"version": "0.2.1",
"preview": true,
"description": "Actions glob lib",
"keywords": [
+2 -3
View File
@@ -26,13 +26,12 @@ export async function create(
*/
export async function hashFiles(
patterns: string,
options?: HashFileOptions,
verbose: Boolean = false
options?: HashFileOptions
): Promise<string> {
let followSymbolicLinks = true
if (options && typeof options.followSymbolicLinks === 'boolean') {
followSymbolicLinks = options.followSymbolicLinks
}
const globber = await create(patterns, {followSymbolicLinks})
return _hashFiles(globber, verbose)
return _hashFiles(globber)
}
+6 -10
View File
@@ -6,23 +6,19 @@ import * as util from 'util'
import * as path from 'path'
import {Globber} from './glob'
export async function hashFiles(
globber: Globber,
verbose: Boolean = false
): Promise<string> {
const writeDelegate = verbose ? core.info : core.debug
export async function hashFiles(globber: Globber): Promise<string> {
let hasMatch = false
const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd()
const result = crypto.createHash('sha256')
let count = 0
for await (const file of globber.globGenerator()) {
writeDelegate(file)
core.debug(file)
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
writeDelegate(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`)
core.debug(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`)
continue
}
if (fs.statSync(file).isDirectory()) {
writeDelegate(`Skip directory '${file}'.`)
core.debug(`Skip directory '${file}'.`)
continue
}
const hash = crypto.createHash('sha256')
@@ -37,10 +33,10 @@ export async function hashFiles(
result.end()
if (hasMatch) {
writeDelegate(`Found ${count} files to hash.`)
core.debug(`Found ${count} files to hash.`)
return result.digest('hex')
} else {
writeDelegate(`No matches found for glob`)
core.debug(`No matches found for glob`)
return ''
}
}
-5
View File
@@ -1,5 +0,0 @@
_out
node_modules
.DS_Store
testoutput.txt
npm-debug.log
-21
View File
@@ -1,21 +0,0 @@
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.
-79
View File
@@ -1,79 +0,0 @@
<p align="center">
<img src="actions.png">
</p>
# Actions Http-Client
[![Http Status](https://github.com/actions/http-client/workflows/http-tests/badge.svg)](https://github.com/actions/http-client/actions)
A lightweight HTTP client optimized for use with actions, TypeScript with generics and async await.
## Features
- HTTP client with TypeScript generics and async/await/Promises
- Typings included so no need to acquire separately (great for intellisense and no versioning drift)
- [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 [HTTP](./__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 [HTTP tests](./__tests__) for detailed examples.
## Debugging
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
```
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:
```bash
$ npm install
```
To build:
```bash
$ npm run build
```
To run all tests:
```bash
$ npm test
```
-26
View File
@@ -1,26 +0,0 @@
## Releases
## 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.
@@ -1,61 +0,0 @@
import * as httpm from '../_out'
import * as am from '../_out/auth'
describe('auth', () => {
beforeEach(() => {})
afterEach(() => {})
it('does basic http get request with basic auth', async () => {
let bh: am.BasicCredentialHandler = new am.BasicCredentialHandler(
'johndoe',
'password'
)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
let 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 () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
token
)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
let 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 () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
expect(auth).toBe('Bearer ' + token)
expect(obj.url).toBe('http://httpbin.org/get')
})
})
@@ -1,375 +0,0 @@
import * as httpm from '../_out'
import * as ifm from '../_out/interfaces'
import * as path from 'path'
import * as fs from 'fs'
let 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', () => {
let 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 done => {
let res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeTruthy()
done()
})
it('does basic http get request with no user agent', async done => {
let http: httpm.HttpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeFalsy()
done()
})
it('does basic https get request', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('does basic http get request with default headers', async done => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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')
done()
})
it('does basic http get request with merged headers', async done => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
})
let res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get',
{
'content-type': 'application/x-www-form-urlencoded'
}
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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')
done()
})
it('pipes a get request', () => {
return new Promise<string>(async (resolve, reject) => {
let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://httpbin.org/get')).message
.pipe(file)
.on('close', () => {
let body: string = fs.readFileSync(sampleFilePath).toString()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
resolve()
})
})
})
it('does basic get request with redirects', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/get')
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('does basic get request with redirects (303)', async done => {
let 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)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('returns 404 for not found get request on redirect', async done => {
let 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)
let body: string = await res.readBody()
done()
})
it('does not follow redirects if disabled', async done => {
let http: httpm.HttpClient = new httpm.HttpClient(
'typed-test-client-tests',
null,
{allowRedirects: false}
)
let res: httpm.HttpClientResponse = await http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/get')
)
expect(res.message.statusCode).toBe(302)
let body: string = await res.readBody()
done()
})
it('does not pass auth with diff hostname redirects', async done => {
let headers = {
accept: 'application/json',
authorization: 'shhh'
}
let 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)
let body: string = await res.readBody()
let obj: any = 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')
done()
})
it('does not pass Auth with diff hostname redirects', async done => {
let headers = {
Accept: 'application/json',
Authorization: 'shhh'
}
let 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)
let body: string = await res.readBody()
let obj: any = 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')
done()
})
it('does basic head request', async done => {
let res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
done()
})
it('does basic http delete request', async done => {
let res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
done()
})
it('does basic http post request', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post')
done()
})
it('does basic http patch request', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch')
done()
})
it('does basic http options request', async done => {
let res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
done()
})
it('returns 404 for not found get request', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/status/404'
)
expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody()
done()
})
it('gets a json object', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = 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 () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson<
HttpBinData
>('https://httpbin.org/status/404')
expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull()
})
it('posts a json object', async () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = 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 () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = 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 () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = 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
)
})
})
@@ -1,115 +0,0 @@
import * as httpm from '../_out'
import * as ifm from '../_out/interfaces'
describe('headers', () => {
let _http: httpm.HttpClient
beforeEach(() => {
_http = new httpm.HttpClient('http-client-tests')
})
it('preserves existing headers on getJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = 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
)
let 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 () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = 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
)
let 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 () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = 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
)
let 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 () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = 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
)
let 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
)
})
})
@@ -1,79 +0,0 @@
import * as httpm from '../_out'
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 done => {
let res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
done()
})
it('does basic head request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
done()
})
it('does basic http delete request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
done()
})
it('does basic http post request with keepAlive true', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post')
done()
})
it('does basic http patch request with keepAlive true', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch')
done()
})
it('does basic http options request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
done()
})
})
@@ -1,228 +0,0 @@
import * as http from 'http'
import * as httpm from '../_out'
import * as pm from '../_out/proxy'
import * as proxy from 'proxy'
import * as tunnelm from 'tunnel'
let _proxyConnects: string[]
let _proxyServer: http.Server
let _proxyUrl = 'http://127.0.0.1:8080'
describe('proxy', () => {
beforeAll(async () => {
// Start proxy server
_proxyServer = proxy()
await new Promise(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(resolve => {
_proxyServer.once('close', () => resolve())
_proxyServer.close()
})
})
it('getProxyUrl does not return proxyUrl if variables not set', () => {
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let 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'
let bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy()
})
it('checkBypass returns false if empty no_proxy', () => {
process.env['no_proxy'] = ''
let 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()
let res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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()
let res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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()
let res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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()
let res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = 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()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url')
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()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url')
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() {
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
}
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
{
"name": "@actions/http-client",
"version": "1.0.11",
"description": "Actions Http Client",
"main": "index.js",
"scripts": {
"build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out",
"test": "jest",
"format": "prettier --write *.ts && prettier --write **/*.ts",
"format-check": "prettier --check *.ts && prettier --check **/*.ts",
"audit-check": "npm audit --audit-level=moderate"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/http-client.git"
},
"keywords": [
"Actions",
"Http"
],
"author": "GitHub, Inc.",
"license": "MIT",
"bugs": {
"url": "https://github.com/actions/http-client/issues"
},
"homepage": "https://github.com/actions/http-client#readme",
"devDependencies": {
"@types/jest": "^25.1.4",
"@types/node": "^12.12.31",
"jest": "^25.1.0",
"prettier": "^2.0.4",
"proxy": "^1.0.1",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
},
"dependencies": {
"tunnel": "0.0.6"
}
}
-86
View File
@@ -1,86 +0,0 @@
import ifm = require('./interfaces')
export class BasicCredentialHandler implements ifm.IRequestHandler {
username: string
password: string
constructor(username: string, password: string) {
this.username = username
this.password = password
}
prepareRequest(options: any): void {
options.headers['Authorization'] =
'Basic ' +
Buffer.from(this.username + ':' + this.password).toString('base64')
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
}
}
export class BearerCredentialHandler implements ifm.IRequestHandler {
token: string
constructor(token: string) {
this.token = token
}
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void {
options.headers['Authorization'] = 'Bearer ' + this.token
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
}
}
export class PersonalAccessTokenCredentialHandler
implements ifm.IRequestHandler {
token: string
constructor(token: string) {
this.token = token
}
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void {
options.headers['Authorization'] =
'Basic ' + Buffer.from('PAT:' + this.token).toString('base64')
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
}
}
-768
View File
@@ -1,768 +0,0 @@
import http = require('http')
import https = require('https')
import ifm = require('./interfaces')
import pm = require('./proxy')
let tunnel: any
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 {
let 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)
}
public statusCode: number
public result?: any
}
export class HttpClientResponse implements ifm.IHttpClientResponse {
constructor(message: http.IncomingMessage) {
this.message = message
}
public message: http.IncomingMessage
readBody(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
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) {
let parsedUrl: URL = new URL(requestUrl)
return parsedUrl.protocol === 'https:'
}
export class HttpClient {
userAgent: string | undefined
handlers: ifm.IRequestHandler[]
requestOptions: ifm.IRequestOptions
private _ignoreSslError: boolean = false
private _socketTimeout: number
private _allowRedirects: boolean = true
private _allowRedirectDowngrade: boolean = false
private _maxRedirects: number = 50
private _allowRetries: boolean = false
private _maxRetries: number = 1
private _agent
private _proxyAgent
private _keepAlive: boolean = false
private _disposed: boolean = false
constructor(
userAgent?: string,
handlers?: ifm.IRequestHandler[],
requestOptions?: ifm.IRequestOptions
) {
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
}
}
}
public options(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
}
public get(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('GET', requestUrl, null, additionalHeaders || {})
}
public del(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('DELETE', requestUrl, null, additionalHeaders || {})
}
public post(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('POST', requestUrl, data, additionalHeaders || {})
}
public patch(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('PATCH', requestUrl, data, additionalHeaders || {})
}
public put(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('PUT', requestUrl, data, additionalHeaders || {})
}
public head(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
return this.request('HEAD', requestUrl, null, additionalHeaders || {})
}
public sendStream(
verb: string,
requestUrl: string,
stream: NodeJS.ReadableStream,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
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
*/
public async getJson<T>(
requestUrl: string,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders,
Headers.Accept,
MediaTypes.ApplicationJson
)
let res: ifm.IHttpClientResponse = await this.get(
requestUrl,
additionalHeaders
)
return this._processResponse<T>(res, this.requestOptions)
}
public async postJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let 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
)
let res: ifm.IHttpClientResponse = await this.post(
requestUrl,
data,
additionalHeaders
)
return this._processResponse<T>(res, this.requestOptions)
}
public async putJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let 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
)
let res: ifm.IHttpClientResponse = await this.put(
requestUrl,
data,
additionalHeaders
)
return this._processResponse<T>(res, this.requestOptions)
}
public async patchJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let 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
)
let res: ifm.IHttpClientResponse = 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
*/
public async request(
verb: string,
requestUrl: string,
data: string | NodeJS.ReadableStream,
headers: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
if (this._disposed) {
throw new Error('Client has already been disposed.')
}
let parsedUrl = new URL(requestUrl)
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers)
// Only perform retries on reads since writes may not be idempotent.
let maxTries: number =
this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
? this._maxRetries + 1
: 1
let numTries: number = 0
let response: HttpClientResponse
while (numTries < maxTries) {
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.IRequestHandler
for (let i = 0; i < this.handlers.length; i++) {
if (this.handlers[i].canHandleAuthentication(response)) {
authenticationHandler = this.handlers[i]
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 (
HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
this._allowRedirects &&
redirectsRemaining > 0
) {
const redirectUrl: string | null = response.message.headers['location']
if (!redirectUrl) {
// if there's no location to redirect to, we won't
break
}
let 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 (let 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 (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
// If not a retry code, return immediately instead of retrying
return response
}
numTries += 1
if (numTries < maxTries) {
await response.readBody()
await this._performExponentialBackoff(numTries)
}
}
return response
}
/**
* Needs to be called if keepAlive is set to true in request options.
*/
public dispose() {
if (this._agent) {
this._agent.destroy()
}
this._disposed = true
}
/**
* Raw request.
* @param info
* @param data
*/
public requestRaw(
info: ifm.IRequestInfo,
data: string | NodeJS.ReadableStream
): Promise<ifm.IHttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => {
let callbackForResult = function (
err: any,
res: ifm.IHttpClientResponse
) {
if (err) {
reject(err)
}
resolve(res)
}
this.requestRawWithCallback(info, data, callbackForResult)
})
}
/**
* Raw request with callback.
* @param info
* @param data
* @param onResult
*/
public requestRawWithCallback(
info: ifm.IRequestInfo,
data: string | NodeJS.ReadableStream,
onResult: (err: any, res: ifm.IHttpClientResponse) => void
): void {
let socket
if (typeof data === 'string') {
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
}
let callbackCalled: boolean = false
let handleResult = (err: any, res: HttpClientResponse) => {
if (!callbackCalled) {
callbackCalled = true
onResult(err, res)
}
}
let req: http.ClientRequest = info.httpModule.request(
info.options,
(msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg)
handleResult(null, res)
}
)
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), null)
})
req.on('error', function (err) {
// err has statusCode property
// res should have headers
handleResult(err, null)
})
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
*/
public getAgent(serverUrl: string): http.Agent {
let parsedUrl = new URL(serverUrl)
return this._getAgent(parsedUrl)
}
private _prepareRequest(
method: string,
requestUrl: URL,
headers: ifm.IHeaders
): ifm.IRequestInfo {
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{}
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) {
this.handlers.forEach(handler => {
handler.prepareRequest(info.options)
})
}
return info
}
private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders {
const lowercaseKeys = obj =>
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
if (this.requestOptions && this.requestOptions.headers) {
return Object.assign(
{},
lowercaseKeys(this.requestOptions.headers),
lowercaseKeys(headers)
)
}
return lowercaseKeys(headers || {})
}
private _getExistingOrDefaultHeader(
additionalHeaders: ifm.IHeaders,
header: string,
_default: string
) {
const lowercaseKeys = obj =>
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
let clientHeader: string
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
let proxyUrl: URL = pm.getProxyUrl(parsedUrl)
let 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
}
if (useProxy) {
// If using proxy, need tunnel
if (!tunnel) {
tunnel = require('tunnel')
}
const agentOptions = {
maxSockets: 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: 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 _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 static dateTimeDeserializer(key: any, value: any): any {
if (typeof value === 'string') {
let a = new Date(value)
if (!isNaN(a.valueOf())) {
return a
}
}
return value
}
private async _processResponse<T>(
res: ifm.IHttpClientResponse,
options: ifm.IRequestOptions
): Promise<ifm.ITypedResponse<T>> {
return new Promise<ifm.ITypedResponse<T>>(async (resolve, reject) => {
const statusCode: number = res.message.statusCode
const response: ifm.ITypedResponse<T> = {
statusCode: statusCode,
result: null,
headers: {}
}
// not found leads to null obj returned
if (statusCode == HttpCodes.NotFound) {
resolve(response)
}
let obj: any
let contents: string
// get the result from the body
try {
contents = await res.readBody()
if (contents && contents.length > 0) {
if (options && options.deserializeDates) {
obj = JSON.parse(contents, HttpClient.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 + ')'
}
let err = new HttpClientError(msg, statusCode)
err.result = response.result
reject(err)
} else {
resolve(response)
}
})
}
}
-98
View File
@@ -1,98 +0,0 @@
import http = require('http')
export interface IHeaders {
[key: string]: any
}
export interface IHttpClient {
options(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
get(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
del(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
post(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
patch(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
put(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
sendStream(
verb: string,
requestUrl: string,
stream: NodeJS.ReadableStream,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
request(
verb: string,
requestUrl: string,
data: string | NodeJS.ReadableStream,
headers: IHeaders
): Promise<IHttpClientResponse>
requestRaw(
info: IRequestInfo,
data: string | NodeJS.ReadableStream
): Promise<IHttpClientResponse>
requestRawWithCallback(
info: IRequestInfo,
data: string | NodeJS.ReadableStream,
onResult: (err: any, res: IHttpClientResponse) => void
): void
}
export interface IRequestHandler {
prepareRequest(options: http.RequestOptions): void
canHandleAuthentication(response: IHttpClientResponse): boolean
handleAuthentication(
httpClient: IHttpClient,
requestInfo: IRequestInfo,
objs
): Promise<IHttpClientResponse>
}
export interface IHttpClientResponse {
message: http.IncomingMessage
readBody(): Promise<string>
}
export interface IRequestInfo {
options: http.RequestOptions
parsedUrl: URL
httpModule: any
}
export interface IRequestOptions {
headers?: IHeaders
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 ITypedResponse<T> {
statusCode: number
result: T | null
headers: Object
}
-60
View File
@@ -1,60 +0,0 @@
export function getProxyUrl(reqUrl: URL): URL | undefined {
let usingSsl = reqUrl.protocol === 'https:'
let proxyUrl: URL
if (checkBypass(reqUrl)) {
return proxyUrl
}
let proxyVar: string
if (usingSsl) {
proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY']
} else {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY']
}
if (proxyVar) {
proxyUrl = new URL(proxyVar)
}
return proxyUrl
}
export function checkBypass(reqUrl: URL): boolean {
if (!reqUrl.hostname) {
return false
}
let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
if (!noProxy) {
return false
}
// Determine the request port
let reqPort: number
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
let upperReqHosts = [reqUrl.hostname.toUpperCase()]
if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
}
// Compare request host against noproxy
for (let upperNoProxyItem of noProxy
.split(',')
.map(x => x.trim().toUpperCase())
.filter(x => x)) {
if (upperReqHosts.some(x => x === upperNoProxyItem)) {
return true
}
}
return false
}
+16 -2
View File
@@ -332,9 +332,23 @@ describe('rmRF', () => {
await assertExists(filePath)
const fd = await fs.open(filePath, 'r')
await io.rmRF(testPath)
await assertNotExists(testPath)
let worked: boolean
try {
await io.rmRF(testPath)
worked = true
} catch (err) {
worked = false
}
if (os.platform() === 'win32') {
expect(worked).toBe(false)
await assertExists(testPath)
} else {
expect(worked).toBe(true)
await assertNotExists(testPath)
}
await fd.close()
await io.rmRF(testPath)
+12 -12
View File
@@ -164,9 +164,9 @@
"dev": true
},
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"node_modules/mkdirp": {
@@ -217,9 +217,9 @@
}
},
"node_modules/pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
"dev": true,
"engines": {
"node": "*"
@@ -404,9 +404,9 @@
"dev": true
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
@@ -450,9 +450,9 @@
}
},
"pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
"dev": true
},
"propagate": {