Compare commits

..
Author SHA1 Message Date
Rob Herley eb7ed88d77 Merge branch 'main' into robherley/artifact-digest 2022-05-19 11:45:42 -04:00
Konrad Pabjan 500d0b42fe Bump all packages that have @actions/http-client as a dependency (#1088)
* bump @actions/artifact

* update @actions/cache

* Update @actions/core

* Update @actions/github

* update @actions/tool-cache
2022-05-13 11:12:58 -04:00
Konrad Pabjan 82efa3d285 Bump @actions/http-client in all packages that use it (#1087) 2022-05-13 10:58:36 -04:00
Konrad Pabjan 2abc7c46f8 Bump to version 2.0.1 (#1086) 2022-05-13 10:29:29 -04:00
Konrad Pabjan e48f1d0c54 Make tunnel a prod dependency for http-client (#1085) 2022-05-13 10:19:06 -04:00
Brian Cristante aa676f3cc7 Update other packages to use http-client v2 (#1082)
We moved `@actions/http-client` to be part of the toolkit in https://github.com/actions/toolkit/pull/1062.  We also made some breaking changes to exported types and released v2.

The biggest change in terms of lines of code affected was to get rid of the `I-` prefix for interfaces since TypeScript doesn't follow this convention.

I bumped the patch version of all packages except for `tool-cache`, where I bumped the major version.  The rationale is explained in the release notes for that package.
2022-05-11 17:14:25 -04:00
Rob Herley 925ae6978b Merge pull request #1074 from actions/robherley/core-1.8.0-release
@actions/core 1.8.0 release
2022-05-05 16:24:45 -04:00
Rob Herley e73063a93c @actions/core 1.8.0 release 2022-05-05 20:17:30 +00:00
Rob Herley c4ae214c26 Merge pull request #1073 from actions/robherley/deprecate-markdownsummary
Add & deprecate old markdownSummary export
2022-05-05 16:07:16 -04:00
Rob Herley 07242b37a4 add & deprecate old markdownSummary export 2022-05-05 19:44:13 +00:00
Rob Herley 01aceeaad6 Merge pull request #1072 from actions/robherley/not-markdown-summaries
Rename core's `markdownSummary` extension to `summary`
2022-05-05 14:17:33 -04:00
Rob Herley 3d29fb91d1 sed 's/markdownSummary/summary/g' 2022-05-05 17:29:20 +00:00
Rob Herley 35e5aac523 actually use md5 not sha256 2022-05-05 10:32:09 -04:00
Rob Herley a3c696e88e add md5 and use b64 for digest encodings 2022-05-05 09:26:38 -04:00
Brian Cristante 91b7bf978c Move @actions/http-client into the toolkit (#1062)
💡 See https://github.com/actions/toolkit/pull/1064 for a better diff!

https://github.com/actions/toolkit contains a variety of packages used for building actions.  https://github.com/actions/http-client is one such package, but lives outside of the toolkit.  Moving it inside of the toolkit will improve discoverability and reduce the number of repos we have to keep track of for maintenance tasks (such as github/c2c-actions-service#2937).

I checked with @bryanmacfarlane on the historical decision here.  Apparently it was just inertia from before we released the toolkit as multiple packages.

The benefits here are:
- Have one fewer repo to keep track of
- Signal that this is an HTTP client meant for building actions, not for general use.

## Notes
- `@actions/http-client` will continue to be released as its own package.
- Bumping the package version to **2.0.0**.  Since we're compiling in strict mode now, there are some breaking changes to the exported types.  This is an improvement because the null-unsafe version of`http-client` is currently breaking the safety of null-safe consumers.
- I'm not updating the other packages to use the new version in this PR.  I plan to do that in a follow-up.  We'll hold off on publishing `http-client` v2 to NPM until that's done just in case other changes shake out of it.
2022-05-03 11:10:13 -04:00
Rob Herley b68735e060 misc pr feedback 2022-04-27 22:24:16 +00:00
Rob Herley d5c547c19f update crc header key 2022-04-27 22:22:35 +00:00
Rob Herley 9e285cc3fa crc: update test header data 2022-04-26 23:21:31 +00:00
Rob Herley 3f95e2ea4f crc: update headers & digest stream 2022-04-26 22:59:56 +00:00
Rob Herley fccc5ee6e6 crc: move tbl out of class, more tests 2022-04-26 17:31:17 +00:00
Rob Herley 3d61fe8000 add crc64 & tests 2022-04-26 17:13:40 +00:00
Rob Herley 9387bd7ded Merge branch 'main' into robherley/artifact-digest 2022-04-26 15:54:22 +00:00
Rob Herley e3549a9c58 artifact: tests for stream digest util 2022-04-05 09:31:49 -04:00
Rob Herley c5d1911357 add digest header for uploads 2022-04-04 17:54:15 -04:00
53 changed files with 1556 additions and 11141 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ on:
inputs: inputs:
package: package:
required: true required: true
description: 'core, artifact, cache, exec, github, glob, io, tool-cache' description: 'core, artifact, cache, exec, github, glob, http-client, io, tool-cache'
jobs: jobs:
test: test:
+9
View File
@@ -46,6 +46,15 @@ $ npm install @actions/glob
``` ```
<br/> <br/>
:phone: [@actions/http-client](packages/http-client)
A lightweight HTTP client optimized for building actions. Read more [here](packages/http-client)
```bash
$ npm install @actions/http-client
```
<br/>
:pencil2: [@actions/io](packages/io) :pencil2: [@actions/io](packages/io)
Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io) Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io)
+9 -1
View File
@@ -77,4 +77,12 @@
### 1.0.0 ### 1.0.0
- Update `lockfileVersion` to `v2` in `package-lock.json [#1009](https://github.com/actions/toolkit/pull/1009) - Update `lockfileVersion` to `v2` in `package-lock.json` [#1009](https://github.com/actions/toolkit/pull/1009)
### 1.0.1
- Update to v2.0.0 of `@actions/http-client`
### 1.0.2
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
+57
View File
@@ -0,0 +1,57 @@
import CRC64, {CRC64DigestEncoding} from '../src/internal/crc64'
const fixtures = {
data:
'🚀 👉😎👉 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n',
expected: {
hex: '846CE4ADAD6223ED',
base64: '7SNira3kbIQ=',
buffer: Buffer.from([0xed, 0x23, 0x62, 0xad, 0xad, 0xe4, 0x6c, 0x84])
}
}
function assertEncodings(crc: CRC64): void {
const encodings = Object.keys(fixtures.expected) as CRC64DigestEncoding[]
for (const encoding of encodings) {
expect(crc.digest(encoding)).toEqual(fixtures.expected[encoding])
}
}
describe('@actions/artifact/src/internal/crc64', () => {
it('CRC64 from string', async () => {
const crc = new CRC64()
crc.update(fixtures.data)
assertEncodings(crc)
})
it('CRC64 from buffer', async () => {
const crc = new CRC64()
const buf = Buffer.from(fixtures.data)
crc.update(buf)
assertEncodings(crc)
})
it('CRC64 from split data', async () => {
const crc = new CRC64()
const splits = fixtures.data.split('\n').slice(0, -1)
for (const split of splits) {
crc.update(`${split}\n`)
}
assertEncodings(crc)
})
it('flips 64 bits', async () => {
const tests = [
[BigInt(0), BigInt('0xffffffffffffffff')],
[BigInt('0xffffffffffffffff'), BigInt(0)],
[BigInt('0xdeadbeef'), BigInt('0xffffffff21524110')]
]
for (const [input, expected] of tests) {
expect(CRC64.flip64Bits(input)).toEqual(expected)
}
})
})
+2 -3
View File
@@ -3,7 +3,6 @@ import * as net from 'net'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as configVariables from '../src/internal/config-variables' import * as configVariables from '../src/internal/config-variables'
import {retry} from '../src/internal/requestUtils' import {retry} from '../src/internal/requestUtils'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpClientResponse} from '@actions/http-client' import {HttpClientResponse} from '@actions/http-client'
jest.mock('../src/internal/config-variables') jest.mock('../src/internal/config-variables')
@@ -42,7 +41,7 @@ async function testRetry(
async function handleResponse( async function handleResponse(
testResponseCode: number | undefined testResponseCode: number | undefined
): Promise<IHttpClientResponse> { ): Promise<HttpClientResponse> {
if (!testResponseCode) { if (!testResponseCode) {
throw new Error( throw new Error(
'Test incorrectly set up. reverse.pop() was called too many times so not enough test response codes were supplied' 'Test incorrectly set up. reverse.pop() was called too many times so not enough test response codes were supplied'
@@ -72,7 +71,7 @@ async function emptyMockReadBody(): Promise<string> {
async function setupSingleMockResponse( async function setupSingleMockResponse(
statusCode: number statusCode: number
): Promise<IHttpClientResponse> { ): Promise<HttpClientResponse> {
const mockMessage = new http.IncomingMessage(new net.Socket()) const mockMessage = new http.IncomingMessage(new net.Socket())
const mockReadBody = emptyMockReadBody const mockReadBody = emptyMockReadBody
mockMessage.statusCode = statusCode mockMessage.statusCode = statusCode
+19 -2
View File
@@ -10,6 +10,7 @@ import {
getInitialRetryIntervalInMilliseconds, getInitialRetryIntervalInMilliseconds,
getRetryMultiplier getRetryMultiplier
} from '../src/internal/config-variables' } from '../src/internal/config-variables'
import {Readable} from 'stream'
jest.mock('../src/internal/config-variables') jest.mock('../src/internal/config-variables')
@@ -74,15 +75,20 @@ describe('Utils', () => {
const size = 24 const size = 24
const uncompressedLength = 100 const uncompressedLength = 100
const range = 'bytes 0-199/200' const range = 'bytes 0-199/200'
const digest = {
crc64: 'bSzITYnW/P8=',
md5: 'Xiv1fT9AxLbfadrxk2y3ZvgyN0tPwCWafL/wbi9w8mk='
}
const headers = utils.getUploadHeaders( const headers = utils.getUploadHeaders(
contentType, contentType,
true, true,
true, true,
uncompressedLength, uncompressedLength,
size, size,
range range,
digest
) )
expect(Object.keys(headers).length).toEqual(8) expect(Object.keys(headers).length).toEqual(10)
expect(headers['Accept']).toEqual( expect(headers['Accept']).toEqual(
`application/json;api-version=${utils.getApiVersion()}` `application/json;api-version=${utils.getApiVersion()}`
) )
@@ -93,6 +99,8 @@ describe('Utils', () => {
expect(headers['x-tfs-filelength']).toEqual(uncompressedLength) expect(headers['x-tfs-filelength']).toEqual(uncompressedLength)
expect(headers['Content-Length']).toEqual(size) expect(headers['Content-Length']).toEqual(size)
expect(headers['Content-Range']).toEqual(range) expect(headers['Content-Range']).toEqual(range)
expect(headers['x-actions-results-crc64']).toEqual(digest.crc64)
expect(headers['x-actions-results-md5']).toEqual(digest.md5)
}) })
it('Test constructing upload headers with only required parameter', () => { it('Test constructing upload headers with only required parameter', () => {
@@ -219,4 +227,13 @@ describe('Utils', () => {
const size2 = (await fs.promises.stat(emptyFile2)).size const size2 = (await fs.promises.stat(emptyFile2)).size
expect(size2).toEqual(0) expect(size2).toEqual(0)
}) })
it('Creates a digest from a readable stream', async () => {
const data = 'lorem ipsum'
const stream = Readable.from(data)
const digest = await utils.digestForStream(stream)
expect(digest.crc64).toBe('bSzITYnW/P8=')
expect(digest.md5).toBe('gKdR/eV3AoZAxBkADjPrpg==')
})
}) })
+29 -11
View File
@@ -1,16 +1,16 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "0.6.1", "version": "1.0.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "0.6.1", "version": "1.0.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/http-client": "^1.0.11", "@actions/http-client": "^2.0.1",
"tmp": "^0.2.1", "tmp": "^0.2.1",
"tmp-promise": "^3.0.2" "tmp-promise": "^3.0.2"
}, },
@@ -20,14 +20,14 @@
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.6.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11" "@actions/http-client": "^1.0.11"
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/core/node_modules/@actions/http-client": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
@@ -35,6 +35,14 @@
"tunnel": "0.0.6" "tunnel": "0.0.6"
} }
}, },
"node_modules/@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": {
"tunnel": "^0.0.6"
}
},
"node_modules/@types/tmp": { "node_modules/@types/tmp": {
"version": "0.2.3", "version": "0.2.3",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz",
@@ -187,13 +195,13 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": { "@actions/core": {
"version": "1.6.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"requires": { "requires": {
"@actions/http-client": "^1.0.11" "@actions/http-client": "^1.0.11"
}
}, },
"dependencies": {
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
@@ -201,6 +209,16 @@
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "0.0.6"
} }
}
}
},
"@actions/http-client": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": {
"tunnel": "^0.0.6"
}
}, },
"@types/tmp": { "@types/tmp": {
"version": "0.2.3", "version": "0.2.3",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "1.0.0", "version": "1.0.2",
"preview": true, "preview": true,
"description": "Actions artifact lib", "description": "Actions artifact lib",
"keywords": [ "keywords": [
@@ -38,7 +38,7 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/http-client": "^1.0.11", "@actions/http-client": "^2.0.1",
"tmp": "^0.2.1", "tmp": "^0.2.1",
"tmp-promise": "^3.0.2" "tmp-promise": "^3.0.2"
}, },
+317
View File
@@ -0,0 +1,317 @@
/**
* CRC64: cyclic redundancy check, 64-bits
*
* In order to validate that artifacts are not being corrupted over the wire, this redundancy check allows us to
* validate that there was no corruption during transmission. The implementation here is based on Go's hash/crc64 pkg,
* but without the slicing-by-8 optimization: https://cs.opensource.google/go/go/+/master:src/hash/crc64/crc64.go
*
* This implementation uses a pregenerated table based on 0x9A6C9329AC4BC9B5 as the polynomial, the same polynomial that
* is used for Azure Storage: https://github.com/Azure/azure-storage-net/blob/cbe605f9faa01bfc3003d75fc5a16b2eaccfe102/Lib/Common/Core/Util/Crc64.cs#L27
*/
// when transpile target is >= ES2020 (after dropping node 12) these can be changed to bigint literals - ts(2737)
const PREGEN_POLY_TABLE = [
BigInt('0x0000000000000000'),
BigInt('0x7F6EF0C830358979'),
BigInt('0xFEDDE190606B12F2'),
BigInt('0x81B31158505E9B8B'),
BigInt('0xC962E5739841B68F'),
BigInt('0xB60C15BBA8743FF6'),
BigInt('0x37BF04E3F82AA47D'),
BigInt('0x48D1F42BC81F2D04'),
BigInt('0xA61CECB46814FE75'),
BigInt('0xD9721C7C5821770C'),
BigInt('0x58C10D24087FEC87'),
BigInt('0x27AFFDEC384A65FE'),
BigInt('0x6F7E09C7F05548FA'),
BigInt('0x1010F90FC060C183'),
BigInt('0x91A3E857903E5A08'),
BigInt('0xEECD189FA00BD371'),
BigInt('0x78E0FF3B88BE6F81'),
BigInt('0x078E0FF3B88BE6F8'),
BigInt('0x863D1EABE8D57D73'),
BigInt('0xF953EE63D8E0F40A'),
BigInt('0xB1821A4810FFD90E'),
BigInt('0xCEECEA8020CA5077'),
BigInt('0x4F5FFBD87094CBFC'),
BigInt('0x30310B1040A14285'),
BigInt('0xDEFC138FE0AA91F4'),
BigInt('0xA192E347D09F188D'),
BigInt('0x2021F21F80C18306'),
BigInt('0x5F4F02D7B0F40A7F'),
BigInt('0x179EF6FC78EB277B'),
BigInt('0x68F0063448DEAE02'),
BigInt('0xE943176C18803589'),
BigInt('0x962DE7A428B5BCF0'),
BigInt('0xF1C1FE77117CDF02'),
BigInt('0x8EAF0EBF2149567B'),
BigInt('0x0F1C1FE77117CDF0'),
BigInt('0x7072EF2F41224489'),
BigInt('0x38A31B04893D698D'),
BigInt('0x47CDEBCCB908E0F4'),
BigInt('0xC67EFA94E9567B7F'),
BigInt('0xB9100A5CD963F206'),
BigInt('0x57DD12C379682177'),
BigInt('0x28B3E20B495DA80E'),
BigInt('0xA900F35319033385'),
BigInt('0xD66E039B2936BAFC'),
BigInt('0x9EBFF7B0E12997F8'),
BigInt('0xE1D10778D11C1E81'),
BigInt('0x606216208142850A'),
BigInt('0x1F0CE6E8B1770C73'),
BigInt('0x8921014C99C2B083'),
BigInt('0xF64FF184A9F739FA'),
BigInt('0x77FCE0DCF9A9A271'),
BigInt('0x08921014C99C2B08'),
BigInt('0x4043E43F0183060C'),
BigInt('0x3F2D14F731B68F75'),
BigInt('0xBE9E05AF61E814FE'),
BigInt('0xC1F0F56751DD9D87'),
BigInt('0x2F3DEDF8F1D64EF6'),
BigInt('0x50531D30C1E3C78F'),
BigInt('0xD1E00C6891BD5C04'),
BigInt('0xAE8EFCA0A188D57D'),
BigInt('0xE65F088B6997F879'),
BigInt('0x9931F84359A27100'),
BigInt('0x1882E91B09FCEA8B'),
BigInt('0x67EC19D339C963F2'),
BigInt('0xD75ADABD7A6E2D6F'),
BigInt('0xA8342A754A5BA416'),
BigInt('0x29873B2D1A053F9D'),
BigInt('0x56E9CBE52A30B6E4'),
BigInt('0x1E383FCEE22F9BE0'),
BigInt('0x6156CF06D21A1299'),
BigInt('0xE0E5DE5E82448912'),
BigInt('0x9F8B2E96B271006B'),
BigInt('0x71463609127AD31A'),
BigInt('0x0E28C6C1224F5A63'),
BigInt('0x8F9BD7997211C1E8'),
BigInt('0xF0F5275142244891'),
BigInt('0xB824D37A8A3B6595'),
BigInt('0xC74A23B2BA0EECEC'),
BigInt('0x46F932EAEA507767'),
BigInt('0x3997C222DA65FE1E'),
BigInt('0xAFBA2586F2D042EE'),
BigInt('0xD0D4D54EC2E5CB97'),
BigInt('0x5167C41692BB501C'),
BigInt('0x2E0934DEA28ED965'),
BigInt('0x66D8C0F56A91F461'),
BigInt('0x19B6303D5AA47D18'),
BigInt('0x980521650AFAE693'),
BigInt('0xE76BD1AD3ACF6FEA'),
BigInt('0x09A6C9329AC4BC9B'),
BigInt('0x76C839FAAAF135E2'),
BigInt('0xF77B28A2FAAFAE69'),
BigInt('0x8815D86ACA9A2710'),
BigInt('0xC0C42C4102850A14'),
BigInt('0xBFAADC8932B0836D'),
BigInt('0x3E19CDD162EE18E6'),
BigInt('0x41773D1952DB919F'),
BigInt('0x269B24CA6B12F26D'),
BigInt('0x59F5D4025B277B14'),
BigInt('0xD846C55A0B79E09F'),
BigInt('0xA72835923B4C69E6'),
BigInt('0xEFF9C1B9F35344E2'),
BigInt('0x90973171C366CD9B'),
BigInt('0x1124202993385610'),
BigInt('0x6E4AD0E1A30DDF69'),
BigInt('0x8087C87E03060C18'),
BigInt('0xFFE938B633338561'),
BigInt('0x7E5A29EE636D1EEA'),
BigInt('0x0134D92653589793'),
BigInt('0x49E52D0D9B47BA97'),
BigInt('0x368BDDC5AB7233EE'),
BigInt('0xB738CC9DFB2CA865'),
BigInt('0xC8563C55CB19211C'),
BigInt('0x5E7BDBF1E3AC9DEC'),
BigInt('0x21152B39D3991495'),
BigInt('0xA0A63A6183C78F1E'),
BigInt('0xDFC8CAA9B3F20667'),
BigInt('0x97193E827BED2B63'),
BigInt('0xE877CE4A4BD8A21A'),
BigInt('0x69C4DF121B863991'),
BigInt('0x16AA2FDA2BB3B0E8'),
BigInt('0xF86737458BB86399'),
BigInt('0x8709C78DBB8DEAE0'),
BigInt('0x06BAD6D5EBD3716B'),
BigInt('0x79D4261DDBE6F812'),
BigInt('0x3105D23613F9D516'),
BigInt('0x4E6B22FE23CC5C6F'),
BigInt('0xCFD833A67392C7E4'),
BigInt('0xB0B6C36E43A74E9D'),
BigInt('0x9A6C9329AC4BC9B5'),
BigInt('0xE50263E19C7E40CC'),
BigInt('0x64B172B9CC20DB47'),
BigInt('0x1BDF8271FC15523E'),
BigInt('0x530E765A340A7F3A'),
BigInt('0x2C608692043FF643'),
BigInt('0xADD397CA54616DC8'),
BigInt('0xD2BD67026454E4B1'),
BigInt('0x3C707F9DC45F37C0'),
BigInt('0x431E8F55F46ABEB9'),
BigInt('0xC2AD9E0DA4342532'),
BigInt('0xBDC36EC59401AC4B'),
BigInt('0xF5129AEE5C1E814F'),
BigInt('0x8A7C6A266C2B0836'),
BigInt('0x0BCF7B7E3C7593BD'),
BigInt('0x74A18BB60C401AC4'),
BigInt('0xE28C6C1224F5A634'),
BigInt('0x9DE29CDA14C02F4D'),
BigInt('0x1C518D82449EB4C6'),
BigInt('0x633F7D4A74AB3DBF'),
BigInt('0x2BEE8961BCB410BB'),
BigInt('0x548079A98C8199C2'),
BigInt('0xD53368F1DCDF0249'),
BigInt('0xAA5D9839ECEA8B30'),
BigInt('0x449080A64CE15841'),
BigInt('0x3BFE706E7CD4D138'),
BigInt('0xBA4D61362C8A4AB3'),
BigInt('0xC52391FE1CBFC3CA'),
BigInt('0x8DF265D5D4A0EECE'),
BigInt('0xF29C951DE49567B7'),
BigInt('0x732F8445B4CBFC3C'),
BigInt('0x0C41748D84FE7545'),
BigInt('0x6BAD6D5EBD3716B7'),
BigInt('0x14C39D968D029FCE'),
BigInt('0x95708CCEDD5C0445'),
BigInt('0xEA1E7C06ED698D3C'),
BigInt('0xA2CF882D2576A038'),
BigInt('0xDDA178E515432941'),
BigInt('0x5C1269BD451DB2CA'),
BigInt('0x237C997575283BB3'),
BigInt('0xCDB181EAD523E8C2'),
BigInt('0xB2DF7122E51661BB'),
BigInt('0x336C607AB548FA30'),
BigInt('0x4C0290B2857D7349'),
BigInt('0x04D364994D625E4D'),
BigInt('0x7BBD94517D57D734'),
BigInt('0xFA0E85092D094CBF'),
BigInt('0x856075C11D3CC5C6'),
BigInt('0x134D926535897936'),
BigInt('0x6C2362AD05BCF04F'),
BigInt('0xED9073F555E26BC4'),
BigInt('0x92FE833D65D7E2BD'),
BigInt('0xDA2F7716ADC8CFB9'),
BigInt('0xA54187DE9DFD46C0'),
BigInt('0x24F29686CDA3DD4B'),
BigInt('0x5B9C664EFD965432'),
BigInt('0xB5517ED15D9D8743'),
BigInt('0xCA3F8E196DA80E3A'),
BigInt('0x4B8C9F413DF695B1'),
BigInt('0x34E26F890DC31CC8'),
BigInt('0x7C339BA2C5DC31CC'),
BigInt('0x035D6B6AF5E9B8B5'),
BigInt('0x82EE7A32A5B7233E'),
BigInt('0xFD808AFA9582AA47'),
BigInt('0x4D364994D625E4DA'),
BigInt('0x3258B95CE6106DA3'),
BigInt('0xB3EBA804B64EF628'),
BigInt('0xCC8558CC867B7F51'),
BigInt('0x8454ACE74E645255'),
BigInt('0xFB3A5C2F7E51DB2C'),
BigInt('0x7A894D772E0F40A7'),
BigInt('0x05E7BDBF1E3AC9DE'),
BigInt('0xEB2AA520BE311AAF'),
BigInt('0x944455E88E0493D6'),
BigInt('0x15F744B0DE5A085D'),
BigInt('0x6A99B478EE6F8124'),
BigInt('0x224840532670AC20'),
BigInt('0x5D26B09B16452559'),
BigInt('0xDC95A1C3461BBED2'),
BigInt('0xA3FB510B762E37AB'),
BigInt('0x35D6B6AF5E9B8B5B'),
BigInt('0x4AB846676EAE0222'),
BigInt('0xCB0B573F3EF099A9'),
BigInt('0xB465A7F70EC510D0'),
BigInt('0xFCB453DCC6DA3DD4'),
BigInt('0x83DAA314F6EFB4AD'),
BigInt('0x0269B24CA6B12F26'),
BigInt('0x7D0742849684A65F'),
BigInt('0x93CA5A1B368F752E'),
BigInt('0xECA4AAD306BAFC57'),
BigInt('0x6D17BB8B56E467DC'),
BigInt('0x12794B4366D1EEA5'),
BigInt('0x5AA8BF68AECEC3A1'),
BigInt('0x25C64FA09EFB4AD8'),
BigInt('0xA4755EF8CEA5D153'),
BigInt('0xDB1BAE30FE90582A'),
BigInt('0xBCF7B7E3C7593BD8'),
BigInt('0xC399472BF76CB2A1'),
BigInt('0x422A5673A732292A'),
BigInt('0x3D44A6BB9707A053'),
BigInt('0x759552905F188D57'),
BigInt('0x0AFBA2586F2D042E'),
BigInt('0x8B48B3003F739FA5'),
BigInt('0xF42643C80F4616DC'),
BigInt('0x1AEB5B57AF4DC5AD'),
BigInt('0x6585AB9F9F784CD4'),
BigInt('0xE436BAC7CF26D75F'),
BigInt('0x9B584A0FFF135E26'),
BigInt('0xD389BE24370C7322'),
BigInt('0xACE74EEC0739FA5B'),
BigInt('0x2D545FB4576761D0'),
BigInt('0x523AAF7C6752E8A9'),
BigInt('0xC41748D84FE75459'),
BigInt('0xBB79B8107FD2DD20'),
BigInt('0x3ACAA9482F8C46AB'),
BigInt('0x45A459801FB9CFD2'),
BigInt('0x0D75ADABD7A6E2D6'),
BigInt('0x721B5D63E7936BAF'),
BigInt('0xF3A84C3BB7CDF024'),
BigInt('0x8CC6BCF387F8795D'),
BigInt('0x620BA46C27F3AA2C'),
BigInt('0x1D6554A417C62355'),
BigInt('0x9CD645FC4798B8DE'),
BigInt('0xE3B8B53477AD31A7'),
BigInt('0xAB69411FBFB21CA3'),
BigInt('0xD407B1D78F8795DA'),
BigInt('0x55B4A08FDFD90E51'),
BigInt('0x2ADA5047EFEC8728')
]
export type CRC64DigestEncoding = 'hex' | 'base64' | 'buffer'
class CRC64 {
private _crc: bigint
constructor() {
this._crc = BigInt(0)
}
update(data: Buffer | string): void {
const buffer = typeof data === 'string' ? Buffer.from(data) : data
let crc = CRC64.flip64Bits(this._crc)
for (const dataByte of buffer) {
const crcByte = Number(crc & BigInt(0xff))
crc = PREGEN_POLY_TABLE[crcByte ^ dataByte] ^ (crc >> BigInt(8))
}
this._crc = CRC64.flip64Bits(crc)
}
digest(encoding?: CRC64DigestEncoding): string | Buffer {
switch (encoding) {
case 'hex':
return this._crc.toString(16).toUpperCase()
case 'base64':
return this.toBuffer().toString('base64')
default:
return this.toBuffer()
}
}
private toBuffer(): Buffer {
return Buffer.from(
[0, 8, 16, 24, 32, 40, 48, 56].map(s =>
Number((this._crc >> BigInt(s)) & BigInt(0xff))
)
)
}
static flip64Bits(n: bigint): bigint {
return (BigInt(1) << BigInt(64)) - BigInt(1) - n
}
}
export default CRC64
@@ -18,7 +18,7 @@ import {URL} from 'url'
import {StatusReporter} from './status-reporter' import {StatusReporter} from './status-reporter'
import {performance} from 'perf_hooks' import {performance} from 'perf_hooks'
import {ListArtifactsResponse, QueryArtifactResponse} from './contracts' import {ListArtifactsResponse, QueryArtifactResponse} from './contracts'
import {IHttpClientResponse} from '@actions/http-client/interfaces' import {HttpClientResponse} from '@actions/http-client'
import {HttpManager} from './http-manager' import {HttpManager} from './http-manager'
import {DownloadItem} from './download-specification' import {DownloadItem} from './download-specification'
import {getDownloadFileConcurrency, getRetryLimit} from './config-variables' import {getDownloadFileConcurrency, getRetryLimit} from './config-variables'
@@ -152,7 +152,7 @@ export class DownloadHttpClient {
const headers = getDownloadHeaders('application/json', true, true) const headers = getDownloadHeaders('application/json', true, true)
// a single GET request is used to download a file // a single GET request is used to download a file
const makeDownloadRequest = async (): Promise<IHttpClientResponse> => { const makeDownloadRequest = async (): Promise<HttpClientResponse> => {
const client = this.downloadHttpManager.getClient(httpClientIndex) const client = this.downloadHttpManager.getClient(httpClientIndex)
return await client.get(artifactLocation, headers) return await client.get(artifactLocation, headers)
} }
@@ -225,7 +225,7 @@ export class DownloadHttpClient {
// keep trying to download a file until a retry limit has been reached // keep trying to download a file until a retry limit has been reached
while (retryCount <= retryLimit) { while (retryCount <= retryLimit) {
let response: IHttpClientResponse let response: HttpClientResponse
try { try {
response = await makeDownloadRequest() response = await makeDownloadRequest()
} catch (error) { } catch (error) {
@@ -295,7 +295,7 @@ export class DownloadHttpClient {
* @param isGzip a boolean denoting if the content is compressed using gzip and if we need to decode it * @param isGzip a boolean denoting if the content is compressed using gzip and if we need to decode it
*/ */
async pipeResponseToFile( async pipeResponseToFile(
response: IHttpClientResponse, response: HttpClientResponse,
destinationStream: fs.WriteStream, destinationStream: fs.WriteStream,
isGzip: boolean isGzip: boolean
): Promise<void> { ): Promise<void> {
@@ -1,4 +1,4 @@
import {HttpClient} from '@actions/http-client/index' import {HttpClient} from '@actions/http-client'
import {createHttpClient} from './utils' import {createHttpClient} from './utils'
/** /**
@@ -1,4 +1,4 @@
import {IHttpClientResponse} from '@actions/http-client/interfaces' import {HttpClientResponse} from '@actions/http-client'
import { import {
isRetryableStatusCode, isRetryableStatusCode,
isSuccessStatusCode, isSuccessStatusCode,
@@ -11,11 +11,11 @@ import {getRetryLimit} from './config-variables'
export async function retry( export async function retry(
name: string, name: string,
operation: () => Promise<IHttpClientResponse>, operation: () => Promise<HttpClientResponse>,
customErrorMessages: Map<number, string>, customErrorMessages: Map<number, string>,
maxAttempts: number maxAttempts: number
): Promise<IHttpClientResponse> { ): Promise<HttpClientResponse> {
let response: IHttpClientResponse | undefined = undefined let response: HttpClientResponse | undefined = undefined
let statusCode: number | undefined = undefined let statusCode: number | undefined = undefined
let isRetryable = false let isRetryable = false
let errorMessage = '' let errorMessage = ''
@@ -71,9 +71,9 @@ export async function retry(
export async function retryHttpClientRequest( export async function retryHttpClientRequest(
name: string, name: string,
method: () => Promise<IHttpClientResponse>, method: () => Promise<HttpClientResponse>,
customErrorMessages: Map<number, string> = new Map(), customErrorMessages: Map<number, string> = new Map(),
maxAttempts = getRetryLimit() maxAttempts = getRetryLimit()
): Promise<IHttpClientResponse> { ): Promise<HttpClientResponse> {
return await retry(name, method, customErrorMessages, maxAttempts) return await retry(name, method, customErrorMessages, maxAttempts)
} }
@@ -9,6 +9,7 @@ import {
UploadResults UploadResults
} from './contracts' } from './contracts'
import { import {
digestForStream,
getArtifactUrl, getArtifactUrl,
getContentRange, getContentRange,
getUploadHeaders, getUploadHeaders,
@@ -31,8 +32,7 @@ import {promisify} from 'util'
import {URL} from 'url' import {URL} from 'url'
import {performance} from 'perf_hooks' import {performance} from 'perf_hooks'
import {StatusReporter} from './status-reporter' import {StatusReporter} from './status-reporter'
import {HttpCodes} from '@actions/http-client' import {HttpCodes, HttpClientResponse} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpManager} from './http-manager' import {HttpManager} from './http-manager'
import {UploadSpecification} from './upload-specification' import {UploadSpecification} from './upload-specification'
import {UploadOptions} from './upload-options' import {UploadOptions} from './upload-options'
@@ -406,6 +406,9 @@ export class UploadHttpClient {
isGzip: boolean, isGzip: boolean,
totalFileSize: number totalFileSize: number
): Promise<boolean> { ): Promise<boolean> {
// open a new stream and read it to compute the digest
const digest = await digestForStream(openStream())
// prepare all the necessary headers before making any http call // prepare all the necessary headers before making any http call
const headers = getUploadHeaders( const headers = getUploadHeaders(
'application/octet-stream', 'application/octet-stream',
@@ -413,10 +416,11 @@ export class UploadHttpClient {
isGzip, isGzip,
totalFileSize, totalFileSize,
end - start + 1, end - start + 1,
getContentRange(start, end, uploadFileSize) getContentRange(start, end, uploadFileSize),
digest
) )
const uploadChunkRequest = async (): Promise<IHttpClientResponse> => { const uploadChunkRequest = async (): Promise<HttpClientResponse> => {
const client = this.uploadHttpManager.getClient(httpClientIndex) const client = this.uploadHttpManager.getClient(httpClientIndex)
return await client.sendStream('PUT', resourceUrl, openStream(), headers) return await client.sendStream('PUT', resourceUrl, openStream(), headers)
} }
@@ -427,7 +431,7 @@ export class UploadHttpClient {
// Increments the current retry count and then checks if the retry limit has been reached // Increments the current retry count and then checks if the retry limit has been reached
// If there have been too many retries, fail so the download stops // If there have been too many retries, fail so the download stops
const incrementAndCheckRetryLimit = ( const incrementAndCheckRetryLimit = (
response?: IHttpClientResponse response?: HttpClientResponse
): boolean => { ): boolean => {
retryCount++ retryCount++
if (retryCount > retryLimit) { if (retryCount > retryLimit) {
@@ -464,7 +468,7 @@ export class UploadHttpClient {
// allow for failed chunks to be retried multiple times // allow for failed chunks to be retried multiple times
while (retryCount <= retryLimit) { while (retryCount <= retryLimit) {
let response: IHttpClientResponse let response: HttpClientResponse
try { try {
response = await uploadChunkRequest() response = await uploadChunkRequest()
+43 -11
View File
@@ -1,9 +1,9 @@
import {debug, info, warning} from '@actions/core' import crypto from 'crypto'
import {promises as fs} from 'fs' import {promises as fs} from 'fs'
import {HttpCodes, HttpClient} from '@actions/http-client' import {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'
import {BearerCredentialHandler} from '@actions/http-client/auth' import {debug, info, warning} from '@actions/core'
import {IHeaders, IHttpClientResponse} from '@actions/http-client/interfaces' import {HttpCodes, HttpClient, HttpClientResponse} from '@actions/http-client'
import {IncomingHttpHeaders} from 'http' import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import { import {
getRuntimeToken, getRuntimeToken,
getRuntimeUrl, getRuntimeUrl,
@@ -11,6 +11,7 @@ import {
getRetryMultiplier, getRetryMultiplier,
getInitialRetryIntervalInMilliseconds getInitialRetryIntervalInMilliseconds
} from './config-variables' } from './config-variables'
import CRC64 from './crc64'
/** /**
* Returns a retry time in milliseconds that exponentially gets larger * Returns a retry time in milliseconds that exponentially gets larger
@@ -139,8 +140,8 @@ export function getDownloadHeaders(
contentType: string, contentType: string,
isKeepAlive?: boolean, isKeepAlive?: boolean,
acceptGzip?: boolean acceptGzip?: boolean
): IHeaders { ): OutgoingHttpHeaders {
const requestOptions: IHeaders = {} const requestOptions: OutgoingHttpHeaders = {}
if (contentType) { if (contentType) {
requestOptions['Content-Type'] = contentType requestOptions['Content-Type'] = contentType
@@ -180,9 +181,10 @@ export function getUploadHeaders(
isGzip?: boolean, isGzip?: boolean,
uncompressedLength?: number, uncompressedLength?: number,
contentLength?: number, contentLength?: number,
contentRange?: string contentRange?: string,
): IHeaders { digest?: StreamDigest
const requestOptions: IHeaders = {} ): OutgoingHttpHeaders {
const requestOptions: OutgoingHttpHeaders = {}
requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}` requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`
if (contentType) { if (contentType) {
requestOptions['Content-Type'] = contentType requestOptions['Content-Type'] = contentType
@@ -202,6 +204,10 @@ export function getUploadHeaders(
if (contentRange) { if (contentRange) {
requestOptions['Content-Range'] = contentRange requestOptions['Content-Range'] = contentRange
} }
if (digest) {
requestOptions['x-actions-results-crc64'] = digest.crc64
requestOptions['x-actions-results-md5'] = digest.md5
}
return requestOptions return requestOptions
} }
@@ -227,7 +233,7 @@ export function getArtifactUrl(): string {
* Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided. * Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided.
* Other information such as the headers, the response code and message might be useful, so this is displayed. * Other information such as the headers, the response code and message might be useful, so this is displayed.
*/ */
export function displayHttpDiagnostics(response: IHttpClientResponse): void { export function displayHttpDiagnostics(response: HttpClientResponse): void {
info( info(
`##### Begin Diagnostic HTTP information ##### `##### Begin Diagnostic HTTP information #####
Status Code: ${response.message.statusCode} Status Code: ${response.message.statusCode}
@@ -291,3 +297,29 @@ export function getProperRetention(
export async function sleep(milliseconds: number): Promise<void> { export async function sleep(milliseconds: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, milliseconds)) return new Promise(resolve => setTimeout(resolve, milliseconds))
} }
export interface StreamDigest {
crc64: string
md5: string
}
export async function digestForStream(
stream: NodeJS.ReadableStream
): Promise<StreamDigest> {
return new Promise((resolve, reject) => {
const crc64 = new CRC64()
const md5 = crypto.createHash('md5')
stream
.on('data', data => {
crc64.update(data)
md5.update(data)
})
.on('end', () =>
resolve({
crc64: crc64.digest('base64') as string,
md5: md5.digest('base64')
})
)
.on('error', reject)
})
}
+6
View File
@@ -56,3 +56,9 @@
### 2.0.0 ### 2.0.0
- Added support to check if Actions cache service feature is available or not [#1028](https://github.com/actions/toolkit/pull/1028) - Added support to check if Actions cache service feature is available or not [#1028](https://github.com/actions/toolkit/pull/1028)
### 2.0.3
- Update to v2.0.0 of `@actions/http-client`
### 2.0.4
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
+4 -4
View File
@@ -5,7 +5,7 @@ import * as cacheHttpClient from '../src/internal/cacheHttpClient'
import * as cacheUtils from '../src/internal/cacheUtils' import * as cacheUtils from '../src/internal/cacheUtils'
import {CacheFilename, CompressionMethod} from '../src/internal/constants' import {CacheFilename, CompressionMethod} from '../src/internal/constants'
import * as tar from '../src/internal/tar' import * as tar from '../src/internal/tar'
import {ITypedResponse} from '@actions/http-client/interfaces' import {TypedResponse} from '@actions/http-client/lib/interfaces'
import { import {
ReserveCacheResponse, ReserveCacheResponse,
ITypedResponseWithError ITypedResponseWithError
@@ -172,7 +172,7 @@ test('save with reserve cache failure should fail', async () => {
const reserveCacheMock = jest const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache') .spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => { .mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = { const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500, statusCode: 500,
result: null, result: null,
headers: {} headers: {}
@@ -208,7 +208,7 @@ test('save with server error should fail', async () => {
const reserveCacheMock = jest const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache') .spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => { .mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = { const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500, statusCode: 500,
result: {cacheId}, result: {cacheId},
headers: {} headers: {}
@@ -257,7 +257,7 @@ test('save with valid inputs uploads a cache', async () => {
const reserveCacheMock = jest const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache') .spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => { .mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = { const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500, statusCode: 500,
result: {cacheId}, result: {cacheId},
headers: {} headers: {}
+23 -38
View File
@@ -1,18 +1,18 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "2.0.2", "version": "2.0.4",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/cache", "name": "@actions/cache",
"version": "2.0.0", "version": "2.0.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^1.0.9", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1", "@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0", "@azure/storage-blob": "^12.8.0",
@@ -31,9 +31,9 @@
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
"version": "1.0.4", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": { "dependencies": {
"@actions/io": "^1.0.1" "@actions/io": "^1.0.1"
} }
@@ -48,25 +48,17 @@
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "1.0.9", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": { "dependencies": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
}
},
"node_modules/@actions/http-client/node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
} }
}, },
"node_modules/@actions/io": { "node_modules/@actions/io": {
"version": "1.0.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" "integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
}, },
"node_modules/@azure/abort-controller": { "node_modules/@azure/abort-controller": {
"version": "1.0.4", "version": "1.0.4",
@@ -618,9 +610,9 @@
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
}, },
"@actions/exec": { "@actions/exec": {
"version": "1.0.4", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": { "requires": {
"@actions/io": "^1.0.1" "@actions/io": "^1.0.1"
} }
@@ -635,24 +627,17 @@
} }
}, },
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.9", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
},
"dependencies": {
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
}
} }
}, },
"@actions/io": { "@actions/io": {
"version": "1.0.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg==" "integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
}, },
"@azure/abort-controller": { "@azure/abort-controller": {
"version": "1.0.4", "version": "1.0.4",
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "2.0.2", "version": "2.0.4",
"preview": true, "preview": true,
"description": "Actions cache lib", "description": "Actions cache lib",
"keywords": [ "keywords": [
@@ -40,7 +40,7 @@
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^1.0.9", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1", "@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0", "@azure/storage-blob": "^12.8.0",
@@ -48,8 +48,8 @@
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^3.8.3",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.5" "@types/uuid": "^3.4.5",
"typescript": "^3.8.3"
} }
} }
+8 -5
View File
@@ -1,7 +1,10 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth' import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {IRequestOptions, ITypedResponse} from '@actions/http-client/interfaces' import {
RequestOptions,
TypedResponse
} from '@actions/http-client/lib/interfaces'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import {URL} from 'url' import {URL} from 'url'
@@ -46,8 +49,8 @@ function createAcceptHeader(type: string, apiVersion: string): string {
return `${type};api-version=${apiVersion}` return `${type};api-version=${apiVersion}`
} }
function getRequestOptions(): IRequestOptions { function getRequestOptions(): RequestOptions {
const requestOptions: IRequestOptions = { const requestOptions: RequestOptions = {
headers: { headers: {
Accept: createAcceptHeader('application/json', '6.0-preview.1') Accept: createAcceptHeader('application/json', '6.0-preview.1')
} }
@@ -275,7 +278,7 @@ async function commitCache(
httpClient: HttpClient, httpClient: HttpClient,
cacheId: number, cacheId: number,
filesize: number filesize: number
): Promise<ITypedResponse<null>> { ): Promise<TypedResponse<null>> {
const commitCacheRequest: CommitCacheRequest = {size: filesize} const commitCacheRequest: CommitCacheRequest = {size: filesize}
return await retryTypedResponse('commitCache', async () => return await retryTypedResponse('commitCache', async () =>
httpClient.postJson<null>( httpClient.postJson<null>(
+2 -2
View File
@@ -1,8 +1,8 @@
import {CompressionMethod} from './constants' import {CompressionMethod} from './constants'
import {ITypedResponse} from '@actions/http-client/interfaces' import {TypedResponse} from '@actions/http-client/lib/interfaces'
import {HttpClientError} from '@actions/http-client' import {HttpClientError} from '@actions/http-client'
export interface ITypedResponseWithError<T> extends ITypedResponse<T> { export interface ITypedResponseWithError<T> extends TypedResponse<T> {
error?: HttpClientError error?: HttpClientError
} }
+2 -3
View File
@@ -1,6 +1,5 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {HttpClient} from '@actions/http-client' import {HttpClient, HttpClientResponse} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {BlockBlobClient} from '@azure/storage-blob' import {BlockBlobClient} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/ms-rest-js' import {TransferProgressEvent} from '@azure/ms-rest-js'
import * as buffer from 'buffer' import * as buffer from 'buffer'
@@ -20,7 +19,7 @@ import {retryHttpClientResponse} from './requestUtils'
* @param output the writable stream * @param output the writable stream
*/ */
async function pipeResponseToStream( async function pipeResponseToStream(
response: IHttpClientResponse, response: HttpClientResponse,
output: NodeJS.WritableStream output: NodeJS.WritableStream
): Promise<void> { ): Promise<void> {
const pipeline = util.promisify(stream.pipeline) const pipeline = util.promisify(stream.pipeline)
+9 -6
View File
@@ -1,6 +1,9 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import {HttpCodes, HttpClientError} from '@actions/http-client' import {
import {IHttpClientResponse} from '@actions/http-client/interfaces' HttpCodes,
HttpClientError,
HttpClientResponse
} from '@actions/http-client'
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants' import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
import {ITypedResponseWithError} from './contracts' import {ITypedResponseWithError} from './contracts'
@@ -103,7 +106,7 @@ export async function retryTypedResponse<T>(
maxAttempts, maxAttempts,
delay, delay,
// If the error object contains the statusCode property, extract it and return // If the error object contains the statusCode property, extract it and return
// an ITypedResponse<T> so it can be processed by the retry logic. // an TypedResponse<T> so it can be processed by the retry logic.
(error: Error) => { (error: Error) => {
if (error instanceof HttpClientError) { if (error instanceof HttpClientError) {
return { return {
@@ -121,14 +124,14 @@ export async function retryTypedResponse<T>(
export async function retryHttpClientResponse( export async function retryHttpClientResponse(
name: string, name: string,
method: () => Promise<IHttpClientResponse>, method: () => Promise<HttpClientResponse>,
maxAttempts = DefaultRetryAttempts, maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay delay = DefaultRetryDelay
): Promise<IHttpClientResponse> { ): Promise<HttpClientResponse> {
return await retry( return await retry(
name, name,
method, method,
(response: IHttpClientResponse) => response.message.statusCode, (response: HttpClientResponse) => response.message.statusCode,
maxAttempts, maxAttempts,
delay delay
) )
+11
View File
@@ -1,5 +1,16 @@
# @actions/core Releases # @actions/core Releases
### 1.8.2
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
### 1.8.1
- Update to v2.0.0 of `@actions/http-client`
### 1.8.0
- Deprecate `markdownSummary` extension export in favor of `summary`
- https://github.com/actions/toolkit/pull/1072
- https://github.com/actions/toolkit/pull/1073
### 1.7.0 ### 1.7.0
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014) - [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
@@ -1,9 +1,10 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import path from 'path' import path from 'path'
import {markdownSummary, SUMMARY_ENV_VAR} from '../src/markdown-summary' import {summary, SUMMARY_ENV_VAR} from '../src/summary'
const testFilePath = path.join(__dirname, 'test', 'test-summary.md') const testDirectoryPath = path.join(__dirname, 'test')
const testFilePath = path.join(testDirectoryPath, 'test-summary.md')
async function assertSummary(expected: string): Promise<void> { async function assertSummary(expected: string): Promise<void> {
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'}) const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
@@ -67,11 +68,12 @@ const fixtures = {
} }
} }
describe('@actions/core/src/markdown-summary', () => { describe('@actions/core/src/summary', () => {
beforeEach(async () => { beforeEach(async () => {
process.env[SUMMARY_ENV_VAR] = testFilePath process.env[SUMMARY_ENV_VAR] = testFilePath
await fs.promises.mkdir(testDirectoryPath, {recursive: true})
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
markdownSummary.emptyBuffer() summary.emptyBuffer()
}) })
afterAll(async () => { afterAll(async () => {
@@ -80,39 +82,39 @@ describe('@actions/core/src/markdown-summary', () => {
it('throws if summary env var is undefined', async () => { it('throws if summary env var is undefined', async () => {
process.env[SUMMARY_ENV_VAR] = undefined process.env[SUMMARY_ENV_VAR] = undefined
const write = markdownSummary.addRaw(fixtures.text).write() const write = summary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow() await expect(write).rejects.toThrow()
}) })
it('throws if summary file does not exist', async () => { it('throws if summary file does not exist', async () => {
await fs.promises.unlink(testFilePath) await fs.promises.unlink(testFilePath)
const write = markdownSummary.addRaw(fixtures.text).write() const write = summary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow() await expect(write).rejects.toThrow()
}) })
it('appends text to summary file', async () => { it('appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write() await summary.addRaw(fixtures.text).write()
await assertSummary(`# ${fixtures.text}`) await assertSummary(`# ${fixtures.text}`)
}) })
it('overwrites text to summary file', async () => { it('overwrites text to summary file', async () => {
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write({overwrite: true}) await summary.addRaw(fixtures.text).write({overwrite: true})
await assertSummary(fixtures.text) await assertSummary(fixtures.text)
}) })
it('appends text with EOL to summary file', async () => { it('appends text with EOL to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text, true).write() await summary.addRaw(fixtures.text, true).write()
await assertSummary(`# ${fixtures.text}${os.EOL}`) await assertSummary(`# ${fixtures.text}${os.EOL}`)
}) })
it('chains appends text to summary file', async () => { it('chains appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary await summary
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addRaw(fixtures.text) .addRaw(fixtures.text)
@@ -122,33 +124,33 @@ describe('@actions/core/src/markdown-summary', () => {
it('empties buffer after write', async () => { it('empties buffer after write', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write() await summary.addRaw(fixtures.text).write()
await assertSummary(fixtures.text) await assertSummary(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('returns summary buffer as string', () => { it('returns summary buffer as string', () => {
markdownSummary.addRaw(fixtures.text) summary.addRaw(fixtures.text)
expect(markdownSummary.stringify()).toEqual(fixtures.text) expect(summary.stringify()).toEqual(fixtures.text)
}) })
it('return correct values for isEmptyBuffer', () => { it('return correct values for isEmptyBuffer', () => {
markdownSummary.addRaw(fixtures.text) summary.addRaw(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(false) expect(summary.isEmptyBuffer()).toBe(false)
markdownSummary.emptyBuffer() summary.emptyBuffer()
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('clears a buffer and summary file', async () => { it('clears a buffer and summary file', async () => {
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
await markdownSummary.clear() await summary.clear()
await assertSummary('') await assertSummary('')
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('adds EOL', async () => { it('adds EOL', async () => {
await markdownSummary await summary
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addEOL() .addEOL()
.write() .write()
@@ -156,37 +158,37 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds a code block without language', async () => { it('adds a code block without language', async () => {
await markdownSummary.addCodeBlock(fixtures.code).write() await summary.addCodeBlock(fixtures.code).write()
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}` const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a code block with a language', async () => { it('adds a code block with a language', async () => {
await markdownSummary.addCodeBlock(fixtures.code, 'go').write() await summary.addCodeBlock(fixtures.code, 'go').write()
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}` const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an unordered list', async () => { it('adds an unordered list', async () => {
await markdownSummary.addList(fixtures.list).write() await summary.addList(fixtures.list).write()
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}` const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an ordered list', async () => { it('adds an ordered list', async () => {
await markdownSummary.addList(fixtures.list, true).write() await summary.addList(fixtures.list, true).write()
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}` const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a table', async () => { it('adds a table', async () => {
await markdownSummary.addTable(fixtures.table).write() await summary.addTable(fixtures.table).write()
const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}` const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a details element', async () => { it('adds a details element', async () => {
await markdownSummary await summary
.addDetails(fixtures.details.label, fixtures.details.content) .addDetails(fixtures.details.label, fixtures.details.content)
.write() .write()
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}` const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
@@ -194,13 +196,13 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds an image with alt text', async () => { it('adds an image with alt text', async () => {
await markdownSummary.addImage(fixtures.img.src, fixtures.img.alt).write() await summary.addImage(fixtures.img.src, fixtures.img.alt).write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an image with custom dimensions', async () => { it('adds an image with custom dimensions', async () => {
await markdownSummary await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options) .addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write() .write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -208,7 +210,7 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds an image with custom dimensions', async () => { it('adds an image with custom dimensions', async () => {
await markdownSummary await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options) .addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write() .write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -217,21 +219,21 @@ describe('@actions/core/src/markdown-summary', () => {
it('adds headings h1...h6', async () => { it('adds headings h1...h6', async () => {
for (const i of [1, 2, 3, 4, 5, 6]) { for (const i of [1, 2, 3, 4, 5, 6]) {
markdownSummary.addHeading('heading', i) summary.addHeading('heading', i)
} }
await markdownSummary.write() await summary.write()
const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}` const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds h1 if heading level not specified', async () => { it('adds h1 if heading level not specified', async () => {
await markdownSummary.addHeading('heading').write() await summary.addHeading('heading').write()
const expected = `<h1>heading</h1>${os.EOL}` const expected = `<h1>heading</h1>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('uses h1 if heading level is garbage or out of range', async () => { it('uses h1 if heading level is garbage or out of range', async () => {
await markdownSummary await summary
.addHeading('heading', 'foobar') .addHeading('heading', 'foobar')
.addHeading('heading', 1337) .addHeading('heading', 1337)
.addHeading('heading', -1) .addHeading('heading', -1)
@@ -242,35 +244,31 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds a separator', async () => { it('adds a separator', async () => {
await markdownSummary.addSeparator().write() await summary.addSeparator().write()
const expected = `<hr>${os.EOL}` const expected = `<hr>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a break', async () => { it('adds a break', async () => {
await markdownSummary.addBreak().write() await summary.addBreak().write()
const expected = `<br>${os.EOL}` const expected = `<br>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a quote', async () => { it('adds a quote', async () => {
await markdownSummary.addQuote(fixtures.quote.text).write() await summary.addQuote(fixtures.quote.text).write()
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}` const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a quote with citation', async () => { it('adds a quote with citation', async () => {
await markdownSummary await summary.addQuote(fixtures.quote.text, fixtures.quote.cite).write()
.addQuote(fixtures.quote.text, fixtures.quote.cite)
.write()
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}` const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a link with href', async () => { it('adds a link with href', async () => {
await markdownSummary await summary.addLink(fixtures.link.text, fixtures.link.href).write()
.addLink(fixtures.link.text, fixtures.link.href)
.write()
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}` const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
+11 -11
View File
@@ -1,26 +1,26 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.7.0", "version": "1.8.2",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/core", "name": "@actions/core",
"version": "1.6.0", "version": "1.8.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11" "@actions/http-client": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2" "@types/node": "^12.0.2"
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "1.0.11", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": { "dependencies": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
@@ -40,11 +40,11 @@
}, },
"dependencies": { "dependencies": {
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.11", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"@types/node": { "@types/node": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.7.0", "version": "1.8.2",
"description": "Actions core lib", "description": "Actions core lib",
"keywords": [ "keywords": [
"github", "github",
@@ -36,7 +36,7 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11" "@actions/http-client": "^2.0.1"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2" "@types/node": "^12.0.2"
+7 -2
View File
@@ -361,6 +361,11 @@ export async function getIDToken(aud?: string): Promise<string> {
} }
/** /**
* Markdown summary exports * Summary exports
*/ */
export {markdownSummary} from './markdown-summary' export {summary} from './summary'
/**
* @deprecated use core.summary
*/
export {markdownSummary} from './summary'
+3 -3
View File
@@ -1,8 +1,8 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */ /* eslint-disable @typescript-eslint/no-extraneous-class */
import * as actions_http_client from '@actions/http-client' import * as actions_http_client from '@actions/http-client'
import {IRequestOptions} from '@actions/http-client/interfaces' import {RequestOptions} from '@actions/http-client/lib/interfaces'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth' import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {debug, setSecret} from './core' import {debug, setSecret} from './core'
interface TokenResponse { interface TokenResponse {
value?: string value?: string
@@ -13,7 +13,7 @@ export class OidcClient {
allowRetry = true, allowRetry = true,
maxRetry = 10 maxRetry = 10
): actions_http_client.HttpClient { ): actions_http_client.HttpClient {
const requestOptions: IRequestOptions = { const requestOptions: RequestOptions = {
allowRetries: allowRetry, allowRetries: allowRetry,
maxRetries: maxRetry maxRetries: maxRetry
} }
@@ -4,7 +4,7 @@ const {access, appendFile, writeFile} = promises
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY' export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
export const SUMMARY_DOCS_URL = export const SUMMARY_DOCS_URL =
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary' 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'
export type SummaryTableRow = (SummaryTableCell | string)[] export type SummaryTableRow = (SummaryTableCell | string)[]
@@ -51,7 +51,7 @@ export interface SummaryWriteOptions {
overwrite?: boolean overwrite?: boolean
} }
class MarkdownSummary { class Summary {
private _buffer: string private _buffer: string
private _filePath?: string private _filePath?: string
@@ -73,7 +73,7 @@ class MarkdownSummary {
const pathFromEnv = process.env[SUMMARY_ENV_VAR] const pathFromEnv = process.env[SUMMARY_ENV_VAR]
if (!pathFromEnv) { if (!pathFromEnv) {
throw new Error( throw new Error(
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.` `Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`
) )
} }
@@ -119,9 +119,9 @@ class MarkdownSummary {
* *
* @param {SummaryWriteOptions} [options] (optional) options for write operation * @param {SummaryWriteOptions} [options] (optional) options for write operation
* *
* @returns {Promise<MarkdownSummary>} markdown summary instance * @returns {Promise<Summary>} summary instance
*/ */
async write(options?: SummaryWriteOptions): Promise<MarkdownSummary> { async write(options?: SummaryWriteOptions): Promise<Summary> {
const overwrite = !!options?.overwrite const overwrite = !!options?.overwrite
const filePath = await this.filePath() const filePath = await this.filePath()
const writeFunc = overwrite ? writeFile : appendFile const writeFunc = overwrite ? writeFile : appendFile
@@ -132,9 +132,9 @@ class MarkdownSummary {
/** /**
* Clears the summary buffer and wipes the summary file * Clears the summary buffer and wipes the summary file
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
async clear(): Promise<MarkdownSummary> { async clear(): Promise<Summary> {
return this.emptyBuffer().write({overwrite: true}) return this.emptyBuffer().write({overwrite: true})
} }
@@ -159,9 +159,9 @@ class MarkdownSummary {
/** /**
* Resets the summary buffer without writing to summary file * Resets the summary buffer without writing to summary file
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
emptyBuffer(): MarkdownSummary { emptyBuffer(): Summary {
this._buffer = '' this._buffer = ''
return this return this
} }
@@ -172,9 +172,9 @@ class MarkdownSummary {
* @param {string} text content to add * @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addRaw(text: string, addEOL = false): MarkdownSummary { addRaw(text: string, addEOL = false): Summary {
this._buffer += text this._buffer += text
return addEOL ? this.addEOL() : this return addEOL ? this.addEOL() : this
} }
@@ -182,9 +182,9 @@ class MarkdownSummary {
/** /**
* Adds the operating system-specific end-of-line marker to the buffer * Adds the operating system-specific end-of-line marker to the buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addEOL(): MarkdownSummary { addEOL(): Summary {
return this.addRaw(EOL) return this.addRaw(EOL)
} }
@@ -194,9 +194,9 @@ class MarkdownSummary {
* @param {string} code content to render within fenced code block * @param {string} code content to render within fenced code block
* @param {string} lang (optional) language to syntax highlight code * @param {string} lang (optional) language to syntax highlight code
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addCodeBlock(code: string, lang?: string): MarkdownSummary { addCodeBlock(code: string, lang?: string): Summary {
const attrs = { const attrs = {
...(lang && {lang}) ...(lang && {lang})
} }
@@ -210,9 +210,9 @@ class MarkdownSummary {
* @param {string[]} items list of items to render * @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addList(items: string[], ordered = false): MarkdownSummary { addList(items: string[], ordered = false): Summary {
const tag = ordered ? 'ol' : 'ul' const tag = ordered ? 'ol' : 'ul'
const listItems = items.map(item => this.wrap('li', item)).join('') const listItems = items.map(item => this.wrap('li', item)).join('')
const element = this.wrap(tag, listItems) const element = this.wrap(tag, listItems)
@@ -224,9 +224,9 @@ class MarkdownSummary {
* *
* @param {SummaryTableCell[]} rows table rows * @param {SummaryTableCell[]} rows table rows
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addTable(rows: SummaryTableRow[]): MarkdownSummary { addTable(rows: SummaryTableRow[]): Summary {
const tableBody = rows const tableBody = rows
.map(row => { .map(row => {
const cells = row const cells = row
@@ -260,9 +260,9 @@ class MarkdownSummary {
* @param {string} label text for the closed state * @param {string} label text for the closed state
* @param {string} content collapsable content * @param {string} content collapsable content
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addDetails(label: string, content: string): MarkdownSummary { addDetails(label: string, content: string): Summary {
const element = this.wrap('details', this.wrap('summary', label) + content) const element = this.wrap('details', this.wrap('summary', label) + content)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -274,13 +274,9 @@ class MarkdownSummary {
* @param {string} alt text description of the image * @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes * @param {SummaryImageOptions} options (optional) addition image attributes
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addImage( addImage(src: string, alt: string, options?: SummaryImageOptions): Summary {
src: string,
alt: string,
options?: SummaryImageOptions
): MarkdownSummary {
const {width, height} = options || {} const {width, height} = options || {}
const attrs = { const attrs = {
...(width && {width}), ...(width && {width}),
@@ -297,9 +293,9 @@ class MarkdownSummary {
* @param {string} text heading text * @param {string} text heading text
* @param {number | string} [level=1] (optional) the heading level, default: 1 * @param {number | string} [level=1] (optional) the heading level, default: 1
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addHeading(text: string, level?: number | string): MarkdownSummary { addHeading(text: string, level?: number | string): Summary {
const tag = `h${level}` const tag = `h${level}`
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
? tag ? tag
@@ -311,9 +307,9 @@ class MarkdownSummary {
/** /**
* Adds an HTML thematic break (<hr>) to the summary buffer * Adds an HTML thematic break (<hr>) to the summary buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addSeparator(): MarkdownSummary { addSeparator(): Summary {
const element = this.wrap('hr', null) const element = this.wrap('hr', null)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -321,9 +317,9 @@ class MarkdownSummary {
/** /**
* Adds an HTML line break (<br>) to the summary buffer * Adds an HTML line break (<br>) to the summary buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addBreak(): MarkdownSummary { addBreak(): Summary {
const element = this.wrap('br', null) const element = this.wrap('br', null)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -334,9 +330,9 @@ class MarkdownSummary {
* @param {string} text quote text * @param {string} text quote text
* @param {string} cite (optional) citation url * @param {string} cite (optional) citation url
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addQuote(text: string, cite?: string): MarkdownSummary { addQuote(text: string, cite?: string): Summary {
const attrs = { const attrs = {
...(cite && {cite}) ...(cite && {cite})
} }
@@ -350,13 +346,18 @@ class MarkdownSummary {
* @param {string} text link text/content * @param {string} text link text/content
* @param {string} href hyperlink * @param {string} href hyperlink
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addLink(text: string, href: string): MarkdownSummary { addLink(text: string, href: string): Summary {
const element = this.wrap('a', text, {href}) const element = this.wrap('a', text, {href})
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
} }
// singleton export const _summary = new Summary()
export const markdownSummary = new MarkdownSummary()
/**
* @deprecated use `core.summary`
*/
export const markdownSummary = _summary
export const summary = _summary
+6
View File
@@ -1,5 +1,11 @@
# @actions/github Releases # @actions/github Releases
### 5.0.3
- - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
### 5.0.2
- Update to v2.0.0 of `@actions/http-client`
### 5.0.1 ### 5.0.1
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037) - [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
### 5.0.0 ### 5.0.0
+11 -11
View File
@@ -1,15 +1,15 @@
{ {
"name": "@actions/github", "name": "@actions/github",
"version": "5.0.1", "version": "5.0.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/github", "name": "@actions/github",
"version": "5.0.1", "version": "5.0.2",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11", "@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0", "@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0", "@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0" "@octokit/plugin-rest-endpoint-methods": "^5.13.0"
@@ -19,11 +19,11 @@
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "1.0.11", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": { "dependencies": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"node_modules/@octokit/auth-token": { "node_modules/@octokit/auth-token": {
@@ -361,11 +361,11 @@
}, },
"dependencies": { "dependencies": {
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.11", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"@octokit/auth-token": { "@octokit/auth-token": {
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/github", "name": "@actions/github",
"version": "5.0.1", "version": "5.0.3",
"description": "Actions github lib", "description": "Actions github lib",
"keywords": [ "keywords": [
"github", "github",
@@ -38,7 +38,7 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/http-client": "^1.0.11", "@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0", "@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0", "@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0" "@octokit/plugin-rest-endpoint-methods": "^5.13.0"
+42 -7
View File
@@ -6,7 +6,7 @@
"packages": { "packages": {
"": { "": {
"name": "@actions/glob", "name": "@actions/glob",
"version": "0.2.1", "version": "0.3.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
@@ -14,9 +14,20 @@
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.2.6", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"dependencies": {
"@actions/http-client": "^1.0.11"
}
},
"node_modules/@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "0.0.6"
}
}, },
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.0", "version": "1.0.0",
@@ -47,13 +58,32 @@
"engines": { "engines": {
"node": "*" "node": "*"
} }
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
} }
}, },
"dependencies": { "dependencies": {
"@actions/core": { "@actions/core": {
"version": "1.2.6", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"requires": {
"@actions/http-client": "^1.0.11"
}
},
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
}, },
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
@@ -81,6 +111,11 @@
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
} }
} }
} }
-3
View File
@@ -1,5 +1,2 @@
_out
node_modules
.DS_Store
testoutput.txt testoutput.txt
npm-debug.log npm-debug.log
+13 -19
View File
@@ -1,18 +1,11 @@
# `@actions/http-client`
<p align="center"> A lightweight HTTP client optimized for building actions.
<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 ## Features
- HTTP client with TypeScript generics and async/await/Promises - HTTP client with TypeScript generics and async/await/Promises
- Typings included so no need to acquire separately (great for intellisense and no versioning drift) - Typings included!
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner - [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+. - 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. - Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
@@ -28,7 +21,7 @@ npm install @actions/http-client --save
## Samples ## Samples
See the [HTTP](./__tests__) tests for detailed examples. See the [tests](./__tests__) for detailed examples.
## Errors ## Errors
@@ -39,13 +32,13 @@ 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. * 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. * Redirects (3xx) will be followed by default.
See [HTTP tests](./__tests__) for detailed examples. See the [tests](./__tests__) for detailed examples.
## Debugging ## Debugging
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible: To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
``` ```shell
export NODE_DEBUG=http export NODE_DEBUG=http
``` ```
@@ -63,17 +56,18 @@ We welcome PRs. Please create an issue and if applicable, a design before proce
once: once:
```bash ```
$ npm install npm install
``` ```
To build: To build:
```bash ```
$ npm run build npm run build
``` ```
To run all tests: To run all tests:
```bash
$ npm test ```
npm test
``` ```
+13
View File
@@ -1,5 +1,18 @@
## Releases ## Releases
## 2.0.1
- Fix an issue with missing `tunnel` dependency [#1085](https://github.com/actions/toolkit/pull/1085)
## 2.0.0
- The package is now compiled with TypeScript's [`strict` compiler setting](https://www.typescriptlang.org/tsconfig#strict). To comply with stricter rules:
- Some exported types now include `| null` or `| undefined`, matching their actual behavior.
- Types implementing the method `RequestHandler.handleAuthentication()` now throw an `Error` rather than returning `null` if they do not support handling an HTTP 401 response. Callers can still use `canHandleAuthentication()` to determine if this handling is supported or not.
- Types using `any` have been scoped to more specific types.
- Following TypeScript's naming conventions, exported interfaces no longer begin with the prefix `I-`.
- Delete the `IHttpClientResponse` interface in favor of the `HttpClientResponse` class.
- Delete the `IHeaders` interface in favor of `http.OutgoingHttpHeaders`.
- The source code of the package was moved to build with [actions/toolkit](https://github.com/actions/toolkit).
## 1.0.11 ## 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) Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
+38 -26
View File
@@ -1,5 +1,5 @@
import * as httpm from '../_out' import * as httpm from '../lib'
import * as am from '../_out/auth' import * as am from '../lib/auth'
describe('auth', () => { describe('auth', () => {
beforeEach(() => {}) beforeEach(() => {})
@@ -7,17 +7,21 @@ describe('auth', () => {
afterEach(() => {}) afterEach(() => {})
it('does basic http get request with basic auth', async () => { it('does basic http get request with basic auth', async () => {
let bh: am.BasicCredentialHandler = new am.BasicCredentialHandler( const bh: am.BasicCredentialHandler = new am.BasicCredentialHandler(
'johndoe', 'johndoe',
'password' 'password'
) )
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') bh
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
let creds: string = Buffer.from( const creds: string = Buffer.from(
auth.substring('Basic '.length), auth.substring('Basic '.length),
'base64' 'base64'
).toString() ).toString()
@@ -26,36 +30,44 @@ describe('auth', () => {
}) })
it('does basic http get request with pat token auth', async () => { it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler( const ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
token token
) )
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
let creds: string = Buffer.from( const creds: string = Buffer.from(
auth.substring('Basic '.length), auth.substring('Basic '.length),
'base64' 'base64'
).toString() ).toString()
expect(creds).toBe('PAT:' + token) expect(creds).toBe(`PAT:${token}`)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
}) })
it('does basic http get request with pat token auth', async () => { it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token) const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
expect(auth).toBe('Bearer ' + token) expect(auth).toBe(`Bearer ${token}`)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
}) })
}) })
+146 -147
View File
@@ -1,9 +1,10 @@
import * as httpm from '../_out' /* eslint-disable @typescript-eslint/no-explicit-any */
import * as ifm from '../_out/interfaces'
import * as httpm from '..'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
let sampleFilePath: string = path.join(__dirname, 'testoutput.txt') const sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
interface HttpBinData { interface HttpBinData {
url: string url: string
@@ -23,7 +24,7 @@ describe('basics', () => {
afterEach(() => {}) afterEach(() => {})
it('constructs', () => { it('constructs', () => {
let http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests') const http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests')
expect(http).toBeDefined() expect(http).toBeDefined()
}) })
@@ -39,264 +40,259 @@ describe('basics', () => {
// "url": "https://httpbin.org/get" // "url": "https://httpbin.org/get"
// } // }
it('does basic http get request', async done => { it('does basic http get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeTruthy() expect(obj.headers['User-Agent']).toBeTruthy()
done()
}) })
it('does basic http get request with no user agent', async done => { it('does basic http get request with no user agent', async () => {
let http: httpm.HttpClient = new httpm.HttpClient() const http: httpm.HttpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeFalsy() expect(obj.headers['User-Agent']).toBeFalsy()
done()
}) })
it('does basic https get request', async done => { it('does basic https get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('does basic http get request with default headers', async done => { it('does basic http get request with default headers', async () => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) }
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') )
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json') expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe('application/json') expect(obj.headers['Content-Type']).toBe('application/json')
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('does basic http get request with merged headers', async done => { it('does basic http get request with merged headers', async () => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) }
let res: httpm.HttpClientResponse = await http.get( )
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get', 'http://httpbin.org/get',
{ {
'content-type': 'application/x-www-form-urlencoded' 'content-type': 'application/x-www-form-urlencoded'
} }
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json') expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe( expect(obj.headers['Content-Type']).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
) )
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('pipes a get request', () => { it('pipes a get request', async () => {
return new Promise<string>(async (resolve, reject) => { return new Promise<void>(async resolve => {
let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath) const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://httpbin.org/get')).message ;(await _http.get('https://httpbin.org/get')).message
.pipe(file) .pipe(file)
.on('close', () => { .on('close', () => {
let body: string = fs.readFileSync(sampleFilePath).toString() const body: string = fs.readFileSync(sampleFilePath).toString()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
resolve() resolve()
}) })
}) })
}) })
it('does basic get request with redirects', async done => { it('does basic get request with redirects', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') 'https://httpbin.org/get'
)}`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('does basic get request with redirects (303)', async done => { it('does basic get request with redirects (303)', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') + 'https://httpbin.org/get'
'&status_code=303' )}&status_code=303`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('returns 404 for not found get request on redirect', async done => { it('returns 404 for not found get request on redirect', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/status/404') + 'https://httpbin.org/status/404'
'&status_code=303' )}&status_code=303`
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('does not follow redirects if disabled', async done => { it('does not follow redirects if disabled', async () => {
let http: httpm.HttpClient = new httpm.HttpClient( const http: httpm.HttpClient = new httpm.HttpClient(
'typed-test-client-tests', 'typed-test-client-tests',
null, undefined,
{allowRedirects: false} {allowRedirects: false}
) )
let res: httpm.HttpClientResponse = await http.get( const res: httpm.HttpClientResponse = await http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') 'https://httpbin.org/get'
)}`
) )
expect(res.message.statusCode).toBe(302) expect(res.message.statusCode).toBe(302)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('does not pass auth with diff hostname redirects', async done => { it('does not pass auth with diff hostname redirects', async () => {
let headers = { const headers = {
accept: 'application/json', accept: 'application/json',
authorization: 'shhh' authorization: 'shhh'
} }
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://www.httpbin.org/get'), 'https://www.httpbin.org/get'
)}`,
headers headers
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
// httpbin "fixes" the casing // httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json') expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get') expect(obj.url).toBe('https://www.httpbin.org/get')
done()
}) })
it('does not pass Auth with diff hostname redirects', async done => { it('does not pass Auth with diff hostname redirects', async () => {
let headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: 'shhh' Authorization: 'shhh'
} }
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://www.httpbin.org/get'), 'https://www.httpbin.org/get'
)}`,
headers headers
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
// httpbin "fixes" the casing // httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json') expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get') expect(obj.url).toBe('https://www.httpbin.org/get')
done()
}) })
it('does basic head request', async done => { it('does basic head request', async () => {
let res: httpm.HttpClientResponse = await _http.head( const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
done()
}) })
it('does basic http delete request', async done => { it('does basic http delete request', async () => {
let res: httpm.HttpClientResponse = await _http.del( const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete' 'http://httpbin.org/delete'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) JSON.parse(body)
done()
}) })
it('does basic http post request', async done => { it('does basic http post request', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post( const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post', 'http://httpbin.org/post',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post') expect(obj.url).toBe('http://httpbin.org/post')
done()
}) })
it('does basic http patch request', async done => { it('does basic http patch request', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch( const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch', 'http://httpbin.org/patch',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch') expect(obj.url).toBe('http://httpbin.org/patch')
done()
}) })
it('does basic http options request', async done => { it('does basic http options request', async () => {
let res: httpm.HttpClientResponse = await _http.options( const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org' 'http://httpbin.org'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('returns 404 for not found get request', async done => { it('returns 404 for not found get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/status/404' 'http://httpbin.org/status/404'
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('gets a json object', async () => { it('gets a json object', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson< const jsonObj = await _http.getJson<HttpBinData>('https://httpbin.org/get')
HttpBinData
>('https://httpbin.org/get')
expect(jsonObj.statusCode).toBe(200) expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined() expect(jsonObj.result).toBeDefined()
expect(jsonObj.result.url).toBe('https://httpbin.org/get') expect(jsonObj.result?.url).toBe('https://httpbin.org/get')
expect(jsonObj.result.headers['Accept']).toBe( expect(jsonObj.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
@@ -305,26 +301,27 @@ describe('basics', () => {
}) })
it('getting a non existent json object returns null', async () => { it('getting a non existent json object returns null', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson< const jsonObj = await _http.getJson<HttpBinData>(
HttpBinData 'https://httpbin.org/status/404'
>('https://httpbin.org/status/404') )
expect(jsonObj.statusCode).toBe(404) expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull() expect(jsonObj.result).toBeNull()
}) })
it('posts a json object', async () => { it('posts a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.postJson< const restRes = await _http.postJson<HttpBinData>(
HttpBinData 'https://httpbin.org/post',
>('https://httpbin.org/post', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/post') expect(restRes.result?.url).toBe('https://httpbin.org/post')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -333,19 +330,20 @@ describe('basics', () => {
}) })
it('puts a json object', async () => { it('puts a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.putJson< const restRes = await _http.putJson<HttpBinData>(
HttpBinData 'https://httpbin.org/put',
>('https://httpbin.org/put', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/put') expect(restRes.result?.url).toBe('https://httpbin.org/put')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -354,18 +352,19 @@ describe('basics', () => {
}) })
it('patch a json object', async () => { it('patch a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.patchJson< const restRes = await _http.patchJson<HttpBinData>(
HttpBinData 'https://httpbin.org/patch',
>('https://httpbin.org/patch', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/patch') expect(restRes.result?.url).toBe('https://httpbin.org/patch')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
+15 -14
View File
@@ -1,5 +1,6 @@
import * as httpm from '../_out' /* eslint-disable @typescript-eslint/no-explicit-any */
import * as ifm from '../_out/interfaces'
import * as httpm from '..'
describe('headers', () => { describe('headers', () => {
let _http: httpm.HttpClient let _http: httpm.HttpClient
@@ -9,8 +10,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on getJson', async () => { it('preserves existing headers on getJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.getJson<any>( let jsonObj = await _http.getJson<any>(
'https://httpbin.org/get', 'https://httpbin.org/get',
additionalHeaders additionalHeaders
) )
@@ -19,7 +20,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -33,8 +34,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on postJson', async () => { it('preserves existing headers on postJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.postJson<any>( let jsonObj = await _http.postJson<any>(
'https://httpbin.org/post', 'https://httpbin.org/post',
{}, {},
additionalHeaders additionalHeaders
@@ -44,7 +45,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -61,8 +62,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on putJson', async () => { it('preserves existing headers on putJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.putJson<any>( let jsonObj = await _http.putJson<any>(
'https://httpbin.org/put', 'https://httpbin.org/put',
{}, {},
additionalHeaders additionalHeaders
@@ -72,7 +73,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -86,8 +87,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on patchJson', async () => { it('preserves existing headers on patchJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.patchJson<any>( let jsonObj = await _http.patchJson<any>(
'https://httpbin.org/patch', 'https://httpbin.org/patch',
{}, {},
additionalHeaders additionalHeaders
@@ -97,7 +98,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -1,4 +1,4 @@
import * as httpm from '../_out' import * as httpm from '../lib'
describe('basics', () => { describe('basics', () => {
let _http: httpm.HttpClient let _http: httpm.HttpClient
@@ -11,69 +11,63 @@ describe('basics', () => {
_http.dispose() _http.dispose()
}) })
it('does basic http get request with keepAlive true', async done => { it('does basic http get request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('does basic head request with keepAlive true', async done => { it('does basic head request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.head( const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
done()
}) })
it('does basic http delete request with keepAlive true', async done => { it('does basic http delete request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.del( const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete' 'http://httpbin.org/delete'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) JSON.parse(body)
done()
}) })
it('does basic http post request with keepAlive true', async done => { it('does basic http post request with keepAlive true', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post( const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post', 'http://httpbin.org/post',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post') expect(obj.url).toBe('http://httpbin.org/post')
done()
}) })
it('does basic http patch request with keepAlive true', async done => { it('does basic http patch request with keepAlive true', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch( const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch', 'http://httpbin.org/patch',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch') expect(obj.url).toBe('http://httpbin.org/patch')
done()
}) })
it('does basic http options request with keepAlive true', async done => { it('does basic http options request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.options( const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org' 'http://httpbin.org'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() await res.readBody()
done()
}) })
}) })
+43 -39
View File
@@ -1,18 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as http from 'http' import * as http from 'http'
import * as httpm from '../_out' import * as httpm from '../lib/'
import * as pm from '../_out/proxy' import * as pm from '../lib/proxy'
import * as proxy from 'proxy' // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
import * as tunnelm from 'tunnel' const proxy = require('proxy')
let _proxyConnects: string[] let _proxyConnects: string[]
let _proxyServer: http.Server let _proxyServer: http.Server
let _proxyUrl = 'http://127.0.0.1:8080' const _proxyUrl = 'http://127.0.0.1:8080'
describe('proxy', () => { describe('proxy', () => {
beforeAll(async () => { beforeAll(async () => {
// Start proxy server // Start proxy server
_proxyServer = proxy() _proxyServer = proxy()
await new Promise(resolve => { await new Promise<void>(resolve => {
const port = Number(_proxyUrl.split(':')[2]) const port = Number(_proxyUrl.split(':')[2])
_proxyServer.listen(port, () => resolve()) _proxyServer.listen(port, () => resolve())
}) })
@@ -32,126 +34,126 @@ describe('proxy', () => {
_clearVars() _clearVars()
// Stop proxy server // Stop proxy server
await new Promise(resolve => { await new Promise<void>(resolve => {
_proxyServer.once('close', () => resolve()) _proxyServer.once('close', () => resolve())
_proxyServer.close() _proxyServer.close()
}) })
}) })
it('getProxyUrl does not return proxyUrl if variables not set', () => { it('getProxyUrl does not return proxyUrl if variables not set', () => {
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => { it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => { it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
process.env['http_proxy'] = 'https://myproxysvr' process.env['http_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => { it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com')) const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => { it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://myserver')) const proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => { it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => { it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://myserver')) const proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => { it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com')) const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('checkBypass returns true if host as no_proxy list', () => { it('checkBypass returns true if host as no_proxy list', () => {
process.env['no_proxy'] = 'myserver' process.env['no_proxy'] = 'myserver'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list', () => { it('checkBypass returns true if host in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with spaces', () => { it('checkBypass returns true if host in no_proxy list with spaces', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with port', () => { it('checkBypass returns true if host in no_proxy list with port', () => {
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080')) const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host with port in no_proxy list without port', () => { it('checkBypass returns true if host with port in no_proxy list without port', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080')) const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with default https port', () => { it('checkBypass returns true if host in no_proxy list with default https port', () => {
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with default http port', () => { it('checkBypass returns true if host in no_proxy list with default http port', () => {
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
let bypass = pm.checkBypass(new URL('http://myserver')) const bypass = pm.checkBypass(new URL('http://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns false if host not in no_proxy list', () => { it('checkBypass returns false if host not in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://github.com')) const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy() expect(bypass).toBeFalsy()
}) })
it('checkBypass returns false if empty no_proxy', () => { it('checkBypass returns false if empty no_proxy', () => {
process.env['no_proxy'] = '' process.env['no_proxy'] = ''
let bypass = pm.checkBypass(new URL('https://github.com')) const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy() expect(bypass).toBeFalsy()
}) })
it('HttpClient does basic http get request through proxy', async () => { it('HttpClient does basic http get request through proxy', async () => {
process.env['http_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:80']) expect(_proxyConnects).toEqual(['httpbin.org:80'])
}) })
@@ -160,12 +162,12 @@ describe('proxy', () => {
process.env['http_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org' process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -173,12 +175,12 @@ describe('proxy', () => {
it('HttpClient does basic https get request through proxy', async () => { it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:443']) expect(_proxyConnects).toEqual(['httpbin.org:443'])
}) })
@@ -187,12 +189,12 @@ describe('proxy', () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org' process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -200,7 +202,8 @@ describe('proxy', () => {
it('proxyAuth not set in tunnel agent when authentication is not provided', async () => { it('proxyAuth not set in tunnel agent when authentication is not provided', async () => {
process.env['https_proxy'] = 'http://127.0.0.1:8080' process.env['https_proxy'] = 'http://127.0.0.1:8080'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url') const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent) console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1') expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080') expect(agent.proxyOptions.port).toBe('8080')
@@ -210,7 +213,8 @@ describe('proxy', () => {
it('proxyAuth is set in tunnel agent when authentication is provided', async () => { it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
process.env['https_proxy'] = 'http://user:[email protected]:8080' process.env['https_proxy'] = 'http://user:[email protected]:8080'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url') const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent) console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1') expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080') expect(agent.proxyOptions.port).toBe('8080')
@@ -218,7 +222,7 @@ describe('proxy', () => {
}) })
}) })
function _clearVars() { function _clearVars(): void {
delete process.env.http_proxy delete process.env.http_proxy
delete process.env.HTTP_PROXY delete process.env.HTTP_PROXY
delete process.env.https_proxy delete process.env.https_proxy
+116 -10278
View File
File diff suppressed because it is too large Load Diff
+34 -25
View File
@@ -1,39 +1,48 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "1.0.11", "version": "2.0.1",
"description": "Actions Http Client", "description": "Actions Http Client",
"main": "index.js", "keywords": [
"scripts": { "github",
"build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out", "actions",
"test": "jest", "http"
"format": "prettier --write *.ts && prettier --write **/*.ts", ],
"format-check": "prettier --check *.ts && prettier --check **/*.ts", "homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
"audit-check": "npm audit --audit-level=moderate" "license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"!.DS_Store"
],
"publishConfig": {
"access": "public"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/actions/http-client.git" "url": "git+https://github.com/actions/toolkit.git",
"directory": "packages/http-client"
},
"scripts": {
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"tsc": "tsc"
}, },
"keywords": [
"Actions",
"Http"
],
"author": "GitHub, Inc.",
"license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/actions/http-client/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"homepage": "https://github.com/actions/http-client#readme",
"devDependencies": { "devDependencies": {
"@types/jest": "^25.1.4", "@types/tunnel": "0.0.3",
"@types/node": "^12.12.31", "proxy": "^1.0.1"
"jest": "^25.1.0",
"prettier": "^2.0.4",
"proxy": "^1.0.1",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
}, },
"dependencies": { "dependencies": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
} }
+34 -34
View File
@@ -1,6 +1,8 @@
import ifm = require('./interfaces') import * as http from 'http'
import * as ifm from './interfaces'
import {HttpClientResponse} from './index'
export class BasicCredentialHandler implements ifm.IRequestHandler { export class BasicCredentialHandler implements ifm.RequestHandler {
username: string username: string
password: string password: string
@@ -9,27 +11,26 @@ export class BasicCredentialHandler implements ifm.IRequestHandler {
this.password = password this.password = password
} }
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = if (!options.headers) {
'Basic ' + throw Error('The request has no headers')
Buffer.from(this.username + ':' + this.password).toString('base64') }
options.headers['Authorization'] = `Basic ${Buffer.from(
`${this.username}:${this.password}`
).toString('base64')}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
export class BearerCredentialHandler implements ifm.IRequestHandler { export class BearerCredentialHandler implements ifm.RequestHandler {
token: string token: string
constructor(token: string) { constructor(token: string) {
@@ -38,26 +39,25 @@ export class BearerCredentialHandler implements ifm.IRequestHandler {
// currently implements pre-authorization // currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401 // TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = 'Bearer ' + this.token if (!options.headers) {
throw Error('The request has no headers')
}
options.headers['Authorization'] = `Bearer ${this.token}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
export class PersonalAccessTokenCredentialHandler export class PersonalAccessTokenCredentialHandler
implements ifm.IRequestHandler { implements ifm.RequestHandler {
token: string token: string
constructor(token: string) { constructor(token: string) {
@@ -66,21 +66,21 @@ export class PersonalAccessTokenCredentialHandler
// currently implements pre-authorization // currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401 // TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = if (!options.headers) {
'Basic ' + Buffer.from('PAT:' + this.token).toString('base64') throw Error('The request has no headers')
}
options.headers['Authorization'] = `Basic ${Buffer.from(
`PAT:${this.token}`
).toString('base64')}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
+181 -176
View File
@@ -1,9 +1,11 @@
import http = require('http') /* eslint-disable @typescript-eslint/no-explicit-any */
import https = require('https')
import ifm = require('./interfaces')
import pm = require('./proxy')
let tunnel: any import * as http from 'http'
import * as https from 'https'
import * as ifm from './interfaces'
import * as net from 'net'
import * as pm from './proxy'
import * as tunnel from 'tunnel'
export enum HttpCodes { export enum HttpCodes {
OK = 200, OK = 200,
@@ -49,7 +51,7 @@ export enum MediaTypes {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
export function getProxyUrl(serverUrl: string): string { export function getProxyUrl(serverUrl: string): string {
let proxyUrl = pm.getProxyUrl(new URL(serverUrl)) const proxyUrl = pm.getProxyUrl(new URL(serverUrl))
return proxyUrl ? proxyUrl.href : '' return proxyUrl ? proxyUrl.href : ''
} }
@@ -77,18 +79,18 @@ export class HttpClientError extends Error {
Object.setPrototypeOf(this, HttpClientError.prototype) Object.setPrototypeOf(this, HttpClientError.prototype)
} }
public statusCode: number statusCode: number
public result?: any result?: any
} }
export class HttpClientResponse implements ifm.IHttpClientResponse { export class HttpClientResponse {
constructor(message: http.IncomingMessage) { constructor(message: http.IncomingMessage) {
this.message = message this.message = message
} }
public message: http.IncomingMessage message: http.IncomingMessage
readBody(): Promise<string> { async readBody(): Promise<string> {
return new Promise<string>(async (resolve, reject) => { return new Promise<string>(async resolve => {
let output = Buffer.alloc(0) let output = Buffer.alloc(0)
this.message.on('data', (chunk: Buffer) => { this.message.on('data', (chunk: Buffer) => {
@@ -102,32 +104,32 @@ export class HttpClientResponse implements ifm.IHttpClientResponse {
} }
} }
export function isHttps(requestUrl: string) { export function isHttps(requestUrl: string): boolean {
let parsedUrl: URL = new URL(requestUrl) const parsedUrl: URL = new URL(requestUrl)
return parsedUrl.protocol === 'https:' return parsedUrl.protocol === 'https:'
} }
export class HttpClient { export class HttpClient {
userAgent: string | undefined userAgent: string | undefined
handlers: ifm.IRequestHandler[] handlers: ifm.RequestHandler[]
requestOptions: ifm.IRequestOptions requestOptions: ifm.RequestOptions | undefined
private _ignoreSslError: boolean = false private _ignoreSslError = false
private _socketTimeout: number private _socketTimeout: number | undefined
private _allowRedirects: boolean = true private _allowRedirects = true
private _allowRedirectDowngrade: boolean = false private _allowRedirectDowngrade = false
private _maxRedirects: number = 50 private _maxRedirects = 50
private _allowRetries: boolean = false private _allowRetries = false
private _maxRetries: number = 1 private _maxRetries = 1
private _agent private _agent: any
private _proxyAgent private _proxyAgent: any
private _keepAlive: boolean = false private _keepAlive = false
private _disposed: boolean = false private _disposed = false
constructor( constructor(
userAgent?: string, userAgent?: string,
handlers?: ifm.IRequestHandler[], handlers?: ifm.RequestHandler[],
requestOptions?: ifm.IRequestOptions requestOptions?: ifm.RequestOptions
) { ) {
this.userAgent = userAgent this.userAgent = userAgent
this.handlers = handlers || [] this.handlers = handlers || []
@@ -165,64 +167,64 @@ export class HttpClient {
} }
} }
public options( async options(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}) return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
} }
public get( async get(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('GET', requestUrl, null, additionalHeaders || {}) return this.request('GET', requestUrl, null, additionalHeaders || {})
} }
public del( async del(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('DELETE', requestUrl, null, additionalHeaders || {}) return this.request('DELETE', requestUrl, null, additionalHeaders || {})
} }
public post( async post(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('POST', requestUrl, data, additionalHeaders || {}) return this.request('POST', requestUrl, data, additionalHeaders || {})
} }
public patch( async patch(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('PATCH', requestUrl, data, additionalHeaders || {}) return this.request('PATCH', requestUrl, data, additionalHeaders || {})
} }
public put( async put(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('PUT', requestUrl, data, additionalHeaders || {}) return this.request('PUT', requestUrl, data, additionalHeaders || {})
} }
public head( async head(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('HEAD', requestUrl, null, additionalHeaders || {}) return this.request('HEAD', requestUrl, null, additionalHeaders || {})
} }
public sendStream( async sendStream(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request(verb, requestUrl, stream, additionalHeaders) return this.request(verb, requestUrl, stream, additionalHeaders)
} }
@@ -230,28 +232,28 @@ export class HttpClient {
* Gets a typed object from an endpoint * Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/ */
public async getJson<T>( async getJson<T>(
requestUrl: string, requestUrl: string,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.get( const res: HttpClientResponse = await this.get(
requestUrl, requestUrl,
additionalHeaders additionalHeaders
) )
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async postJson<T>( async postJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -262,7 +264,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.post( const res: HttpClientResponse = await this.post(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -270,12 +272,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async putJson<T>( async putJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -286,7 +288,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.put( const res: HttpClientResponse = await this.put(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -294,12 +296,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async patchJson<T>( async patchJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -310,7 +312,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.patch( const res: HttpClientResponse = await this.patch(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -323,28 +325,28 @@ export class HttpClient {
* All other methods such as get, post, patch, and request ultimately call this. * All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch * Prefer get, del, post and patch
*/ */
public async request( async request(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream | null,
headers: ifm.IHeaders headers?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
if (this._disposed) { if (this._disposed) {
throw new Error('Client has already been disposed.') throw new Error('Client has already been disposed.')
} }
let parsedUrl = new URL(requestUrl) const parsedUrl = new URL(requestUrl)
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers) let info: ifm.RequestInfo = this._prepareRequest(verb, parsedUrl, headers)
// Only perform retries on reads since writes may not be idempotent. // Only perform retries on reads since writes may not be idempotent.
let maxTries: number = const maxTries: number =
this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1 this._allowRetries && RetryableHttpVerbs.includes(verb)
? this._maxRetries + 1 ? this._maxRetries + 1
: 1 : 1
let numTries: number = 0 let numTries = 0
let response: HttpClientResponse let response: HttpClientResponse | undefined
while (numTries < maxTries) { do {
response = await this.requestRaw(info, data) response = await this.requestRaw(info, data)
// Check if it's an authentication challenge // Check if it's an authentication challenge
@@ -353,11 +355,11 @@ export class HttpClient {
response.message && response.message &&
response.message.statusCode === HttpCodes.Unauthorized response.message.statusCode === HttpCodes.Unauthorized
) { ) {
let authenticationHandler: ifm.IRequestHandler let authenticationHandler: ifm.RequestHandler | undefined
for (let i = 0; i < this.handlers.length; i++) { for (const handler of this.handlers) {
if (this.handlers[i].canHandleAuthentication(response)) { if (handler.canHandleAuthentication(response)) {
authenticationHandler = this.handlers[i] authenticationHandler = handler
break break
} }
} }
@@ -373,19 +375,21 @@ export class HttpClient {
let redirectsRemaining: number = this._maxRedirects let redirectsRemaining: number = this._maxRedirects
while ( while (
HttpRedirectCodes.indexOf(response.message.statusCode) != -1 && response.message.statusCode &&
HttpRedirectCodes.includes(response.message.statusCode) &&
this._allowRedirects && this._allowRedirects &&
redirectsRemaining > 0 redirectsRemaining > 0
) { ) {
const redirectUrl: string | null = response.message.headers['location'] const redirectUrl: string | undefined =
response.message.headers['location']
if (!redirectUrl) { if (!redirectUrl) {
// if there's no location to redirect to, we won't // if there's no location to redirect to, we won't
break break
} }
let parsedRedirectUrl = new URL(redirectUrl) const parsedRedirectUrl = new URL(redirectUrl)
if ( if (
parsedUrl.protocol == 'https:' && parsedUrl.protocol === 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol && parsedUrl.protocol !== parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade !this._allowRedirectDowngrade
) { ) {
throw new Error( throw new Error(
@@ -399,7 +403,7 @@ export class HttpClient {
// strip authorization header if redirected to a different hostname // strip authorization header if redirected to a different hostname
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
for (let header in headers) { for (const header in headers) {
// header names are case insensitive // header names are case insensitive
if (header.toLowerCase() === 'authorization') { if (header.toLowerCase() === 'authorization') {
delete headers[header] delete headers[header]
@@ -413,7 +417,10 @@ export class HttpClient {
redirectsRemaining-- redirectsRemaining--
} }
if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) { if (
!response.message.statusCode ||
!HttpResponseRetryCodes.includes(response.message.statusCode)
) {
// If not a retry code, return immediately instead of retrying // If not a retry code, return immediately instead of retrying
return response return response
} }
@@ -424,7 +431,7 @@ export class HttpClient {
await response.readBody() await response.readBody()
await this._performExponentialBackoff(numTries) await this._performExponentialBackoff(numTries)
} }
} } while (numTries < maxTries)
return response return response
} }
@@ -432,7 +439,7 @@ export class HttpClient {
/** /**
* Needs to be called if keepAlive is set to true in request options. * Needs to be called if keepAlive is set to true in request options.
*/ */
public dispose() { dispose(): void {
if (this._agent) { if (this._agent) {
this._agent.destroy() this._agent.destroy()
} }
@@ -445,21 +452,21 @@ export class HttpClient {
* @param info * @param info
* @param data * @param data
*/ */
public requestRaw( async requestRaw(
info: ifm.IRequestInfo, info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream data: string | NodeJS.ReadableStream | null
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => { return new Promise<HttpClientResponse>((resolve, reject) => {
let callbackForResult = function ( function callbackForResult(err?: Error, res?: HttpClientResponse): void {
err: any,
res: ifm.IHttpClientResponse
) {
if (err) { if (err) {
reject(err) reject(err)
} } else if (!res) {
// If `err` is not passed, then `res` must be passed.
reject(new Error('Unknown error'))
} else {
resolve(res) resolve(res)
} }
}
this.requestRawWithCallback(info, data, callbackForResult) this.requestRawWithCallback(info, data, callbackForResult)
}) })
@@ -471,33 +478,35 @@ export class HttpClient {
* @param data * @param data
* @param onResult * @param onResult
*/ */
public requestRawWithCallback( requestRawWithCallback(
info: ifm.IRequestInfo, info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream | null,
onResult: (err: any, res: ifm.IHttpClientResponse) => void onResult: (err?: Error, res?: HttpClientResponse) => void
): void { ): void {
let socket
if (typeof data === 'string') { if (typeof data === 'string') {
if (!info.options.headers) {
info.options.headers = {}
}
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8') info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
} }
let callbackCalled: boolean = false let callbackCalled = false
let handleResult = (err: any, res: HttpClientResponse) => { function handleResult(err?: Error, res?: HttpClientResponse): void {
if (!callbackCalled) { if (!callbackCalled) {
callbackCalled = true callbackCalled = true
onResult(err, res) onResult(err, res)
} }
} }
let req: http.ClientRequest = info.httpModule.request( const req: http.ClientRequest = info.httpModule.request(
info.options, info.options,
(msg: http.IncomingMessage) => { (msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg) const res: HttpClientResponse = new HttpClientResponse(msg)
handleResult(null, res) handleResult(undefined, res)
} }
) )
let socket: net.Socket
req.on('socket', sock => { req.on('socket', sock => {
socket = sock socket = sock
}) })
@@ -507,13 +516,13 @@ export class HttpClient {
if (socket) { if (socket) {
socket.end() socket.end()
} }
handleResult(new Error('Request timeout: ' + info.options.path), null) handleResult(new Error(`Request timeout: ${info.options.path}`))
}) })
req.on('error', function(err) { req.on('error', function(err) {
// err has statusCode property // err has statusCode property
// res should have headers // res should have headers
handleResult(err, null) handleResult(err)
}) })
if (data && typeof data === 'string') { if (data && typeof data === 'string') {
@@ -536,17 +545,17 @@ export class HttpClient {
* routing through a proxy server - depending upon the url and proxy environment variables. * 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 * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
public getAgent(serverUrl: string): http.Agent { getAgent(serverUrl: string): http.Agent {
let parsedUrl = new URL(serverUrl) const parsedUrl = new URL(serverUrl)
return this._getAgent(parsedUrl) return this._getAgent(parsedUrl)
} }
private _prepareRequest( private _prepareRequest(
method: string, method: string,
requestUrl: URL, requestUrl: URL,
headers: ifm.IHeaders headers?: http.OutgoingHttpHeaders
): ifm.IRequestInfo { ): ifm.RequestInfo {
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{} const info: ifm.RequestInfo = <ifm.RequestInfo>{}
info.parsedUrl = requestUrl info.parsedUrl = requestUrl
const usingSsl: boolean = info.parsedUrl.protocol === 'https:' const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
@@ -570,23 +579,22 @@ export class HttpClient {
// gives handlers an opportunity to participate // gives handlers an opportunity to participate
if (this.handlers) { if (this.handlers) {
this.handlers.forEach(handler => { for (const handler of this.handlers) {
handler.prepareRequest(info.options) handler.prepareRequest(info.options)
}) }
} }
return info return info
} }
private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders { private _mergeHeaders(
const lowercaseKeys = obj => headers?: http.OutgoingHttpHeaders
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}) ): http.OutgoingHttpHeaders {
if (this.requestOptions && this.requestOptions.headers) { if (this.requestOptions && this.requestOptions.headers) {
return Object.assign( return Object.assign(
{}, {},
lowercaseKeys(this.requestOptions.headers), lowercaseKeys(this.requestOptions.headers),
lowercaseKeys(headers) lowercaseKeys(headers || {})
) )
} }
@@ -594,14 +602,11 @@ export class HttpClient {
} }
private _getExistingOrDefaultHeader( private _getExistingOrDefaultHeader(
additionalHeaders: ifm.IHeaders, additionalHeaders: http.OutgoingHttpHeaders,
header: string, header: string,
_default: string _default: string
) { ): string | number | string[] {
const lowercaseKeys = obj => let clientHeader: string | undefined
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
let clientHeader: string
if (this.requestOptions && this.requestOptions.headers) { if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header] clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
} }
@@ -610,8 +615,8 @@ export class HttpClient {
private _getAgent(parsedUrl: URL): http.Agent { private _getAgent(parsedUrl: URL): http.Agent {
let agent let agent
let proxyUrl: URL = pm.getProxyUrl(parsedUrl) const proxyUrl = pm.getProxyUrl(parsedUrl)
let useProxy = proxyUrl && proxyUrl.hostname const useProxy = proxyUrl && proxyUrl.hostname
if (this._keepAlive && useProxy) { if (this._keepAlive && useProxy) {
agent = this._proxyAgent agent = this._proxyAgent
@@ -622,24 +627,20 @@ export class HttpClient {
} }
// if agent is already assigned use that agent. // if agent is already assigned use that agent.
if (!!agent) { if (agent) {
return agent return agent
} }
const usingSsl = parsedUrl.protocol === 'https:' const usingSsl = parsedUrl.protocol === 'https:'
let maxSockets = 100 let maxSockets = 100
if (!!this.requestOptions) { if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
} }
if (useProxy) { // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
// If using proxy, need tunnel if (proxyUrl && proxyUrl.hostname) {
if (!tunnel) {
tunnel = require('tunnel')
}
const agentOptions = { const agentOptions = {
maxSockets: maxSockets, maxSockets,
keepAlive: this._keepAlive, keepAlive: this._keepAlive,
proxy: { proxy: {
...((proxyUrl.username || proxyUrl.password) && { ...((proxyUrl.username || proxyUrl.password) && {
@@ -664,7 +665,7 @@ export class HttpClient {
// if reusing agent across request and tunneling agent isn't assigned create a new agent // if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) { if (this._keepAlive && !agent) {
const options = {keepAlive: this._keepAlive, maxSockets: maxSockets} const options = {keepAlive: this._keepAlive, maxSockets}
agent = usingSsl ? new https.Agent(options) : new http.Agent(options) agent = usingSsl ? new https.Agent(options) : new http.Agent(options)
this._agent = agent this._agent = agent
} }
@@ -686,15 +687,35 @@ export class HttpClient {
return agent return agent
} }
private _performExponentialBackoff(retryNumber: number): Promise<void> { private async _performExponentialBackoff(retryNumber: number): Promise<void> {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber) retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber) const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
return new Promise(resolve => setTimeout(() => resolve(), ms)) return new Promise(resolve => setTimeout(() => resolve(), ms))
} }
private static dateTimeDeserializer(key: any, value: any): any { private async _processResponse<T>(
res: HttpClientResponse,
options?: ifm.RequestOptions
): Promise<ifm.TypedResponse<T>> {
return new Promise<ifm.TypedResponse<T>>(async (resolve, reject) => {
const statusCode = res.message.statusCode || 0
const response: ifm.TypedResponse<T> = {
statusCode,
result: null,
headers: {}
}
// not found leads to null obj returned
if (statusCode === HttpCodes.NotFound) {
resolve(response)
}
// get the result from the body
function dateTimeDeserializer(key: any, value: any): any {
if (typeof value === 'string') { if (typeof value === 'string') {
let a = new Date(value) const a = new Date(value)
if (!isNaN(a.valueOf())) { if (!isNaN(a.valueOf())) {
return a return a
} }
@@ -703,33 +724,14 @@ export class HttpClient {
return value 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 obj: any
let contents: string let contents: string | undefined
// get the result from the body
try { try {
contents = await res.readBody() contents = await res.readBody()
if (contents && contents.length > 0) { if (contents && contents.length > 0) {
if (options && options.deserializeDates) { if (options && options.deserializeDates) {
obj = JSON.parse(contents, HttpClient.dateTimeDeserializer) obj = JSON.parse(contents, dateTimeDeserializer)
} else { } else {
obj = JSON.parse(contents) obj = JSON.parse(contents)
} }
@@ -753,10 +755,10 @@ export class HttpClient {
// it may be the case that the exception is in the body message as string // it may be the case that the exception is in the body message as string
msg = contents msg = contents
} else { } else {
msg = 'Failed request: (' + statusCode + ')' msg = `Failed request: (${statusCode})`
} }
let err = new HttpClientError(msg, statusCode) const err = new HttpClientError(msg, statusCode)
err.result = response.result err.result = response.result
reject(err) reject(err)
@@ -766,3 +768,6 @@ export class HttpClient {
}) })
} }
} }
const lowercaseKeys = (obj: {[index: string]: any}): any =>
Object.keys(obj).reduce((c: any, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
+36 -43
View File
@@ -1,83 +1,76 @@
import http = require('http') import * as http from 'http'
import * as https from 'https'
import {HttpClientResponse} from './index'
export interface IHeaders { export interface HttpClient {
[key: string]: any
}
export interface IHttpClient {
options( options(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
get( get(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
del( del(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
post( post(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
patch( patch(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
put( put(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
sendStream( sendStream(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
request( request(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream,
headers: IHeaders headers: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
requestRaw( requestRaw(
info: IRequestInfo, info: RequestInfo,
data: string | NodeJS.ReadableStream data: string | NodeJS.ReadableStream
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
requestRawWithCallback( requestRawWithCallback(
info: IRequestInfo, info: RequestInfo,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream,
onResult: (err: any, res: IHttpClientResponse) => void onResult: (err?: Error, res?: HttpClientResponse) => void
): void ): void
} }
export interface IRequestHandler { export interface RequestHandler {
prepareRequest(options: http.RequestOptions): void prepareRequest(options: http.RequestOptions): void
canHandleAuthentication(response: IHttpClientResponse): boolean canHandleAuthentication(response: HttpClientResponse): boolean
handleAuthentication( handleAuthentication(
httpClient: IHttpClient, httpClient: HttpClient,
requestInfo: IRequestInfo, requestInfo: RequestInfo,
objs data: string | NodeJS.ReadableStream | null
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
} }
export interface IHttpClientResponse { export interface RequestInfo {
message: http.IncomingMessage
readBody(): Promise<string>
}
export interface IRequestInfo {
options: http.RequestOptions options: http.RequestOptions
parsedUrl: URL parsedUrl: URL
httpModule: any httpModule: typeof http | typeof https
} }
export interface IRequestOptions { export interface RequestOptions {
headers?: IHeaders headers?: http.OutgoingHttpHeaders
socketTimeout?: number socketTimeout?: number
ignoreSslError?: boolean ignoreSslError?: boolean
allowRedirects?: boolean allowRedirects?: boolean
@@ -91,8 +84,8 @@ export interface IRequestOptions {
maxRetries?: number maxRetries?: number
} }
export interface ITypedResponse<T> { export interface TypedResponse<T> {
statusCode: number statusCode: number
result: T | null result: T | null
headers: Object headers: http.IncomingHttpHeaders
} }
+13 -13
View File
@@ -1,23 +1,23 @@
export function getProxyUrl(reqUrl: URL): URL | undefined { export function getProxyUrl(reqUrl: URL): URL | undefined {
let usingSsl = reqUrl.protocol === 'https:' const usingSsl = reqUrl.protocol === 'https:'
let proxyUrl: URL
if (checkBypass(reqUrl)) { if (checkBypass(reqUrl)) {
return proxyUrl return undefined
} }
let proxyVar: string const proxyVar = (() => {
if (usingSsl) { if (usingSsl) {
proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'] return process.env['https_proxy'] || process.env['HTTPS_PROXY']
} else { } else {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'] return process.env['http_proxy'] || process.env['HTTP_PROXY']
} }
})()
if (proxyVar) { if (proxyVar) {
proxyUrl = new URL(proxyVar) return new URL(proxyVar)
} else {
return undefined
} }
return proxyUrl
} }
export function checkBypass(reqUrl: URL): boolean { export function checkBypass(reqUrl: URL): boolean {
@@ -25,13 +25,13 @@ export function checkBypass(reqUrl: URL): boolean {
return false return false
} }
let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '' const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
if (!noProxy) { if (!noProxy) {
return false return false
} }
// Determine the request port // Determine the request port
let reqPort: number let reqPort: number | undefined
if (reqUrl.port) { if (reqUrl.port) {
reqPort = Number(reqUrl.port) reqPort = Number(reqUrl.port)
} else if (reqUrl.protocol === 'http:') { } else if (reqUrl.protocol === 'http:') {
@@ -41,13 +41,13 @@ export function checkBypass(reqUrl: URL): boolean {
} }
// Format the request hostname and hostname with port // Format the request hostname and hostname with port
let upperReqHosts = [reqUrl.hostname.toUpperCase()] const upperReqHosts = [reqUrl.hostname.toUpperCase()]
if (typeof reqPort === 'number') { if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`) upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
} }
// Compare request host against noproxy // Compare request host against noproxy
for (let upperNoProxyItem of noProxy for (const upperNoProxyItem of noProxy
.split(',') .split(',')
.map(x => x.trim().toUpperCase()) .map(x => x.trim().toUpperCase())
.filter(x => x)) { .filter(x => x)) {
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src",
"moduleResolution": "node"
},
"include": [
"./src"
]
}
+8
View File
@@ -1,5 +1,13 @@
# @actions/tool-cache Releases # @actions/tool-cache Releases
### 2.0.1
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
### 2.0.0
- Update to v2.0.0 of `@actions/http-client`
- The type of the `headers` parameter in the exported function `downloadTool` has been narrowed from `{ [header: string]: any }` to `{ [header: string]: number | string | string[] | undefined; }` (that is, `http.OutgoingHttpHeaders`).
This is strictly a compile-time change for TypeScript consumers. Previous attempts to use a header value of a type other than those now accepted would have resulted in an error at run time.
### 1.7.2 ### 1.7.2
- Update `lockfileVersion` to `v2` in `package-lock.json [#1025](https://github.com/actions/toolkit/pull/1025) - Update `lockfileVersion` to `v2` in `package-lock.json [#1025](https://github.com/actions/toolkit/pull/1025)
+53 -29
View File
@@ -1,17 +1,17 @@
{ {
"name": "@actions/tool-cache", "name": "@actions/tool-cache",
"version": "1.7.2", "version": "2.0.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/tool-cache", "name": "@actions/tool-cache",
"version": "1.7.2", "version": "2.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0",
"uuid": "^3.3.2" "uuid": "^3.3.2"
@@ -24,30 +24,41 @@
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.2.6", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"dependencies": {
"@actions/http-client": "^1.0.11"
}
},
"node_modules/@actions/core/node_modules/@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "0.0.6"
}
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
"version": "1.0.3", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": { "dependencies": {
"@actions/io": "^1.0.1" "@actions/io": "^1.0.1"
} }
}, },
"node_modules/@actions/http-client": { "node_modules/@actions/http-client": {
"version": "1.0.8", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"dependencies": { "dependencies": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"node_modules/@actions/io": { "node_modules/@actions/io": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA==" "integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
}, },
"node_modules/@types/nock": { "node_modules/@types/nock": {
"version": "10.0.3", "version": "10.0.3",
@@ -280,30 +291,43 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": { "@actions/core": {
"version": "1.2.6", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==" "integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"requires": {
"@actions/http-client": "^1.0.11"
},
"dependencies": {
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
}
}
}, },
"@actions/exec": { "@actions/exec": {
"version": "1.0.3", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==", "integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": { "requires": {
"@actions/io": "^1.0.1" "@actions/io": "^1.0.1"
} }
}, },
"@actions/http-client": { "@actions/http-client": {
"version": "1.0.8", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz", "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==", "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
"requires": { "requires": {
"tunnel": "0.0.6" "tunnel": "^0.0.6"
} }
}, },
"@actions/io": { "@actions/io": {
"version": "1.1.1", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA==" "integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
}, },
"@types/nock": { "@types/nock": {
"version": "10.0.3", "version": "10.0.3",
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/tool-cache", "name": "@actions/tool-cache",
"version": "1.7.2", "version": "2.0.1",
"description": "Actions tool-cache lib", "description": "Actions tool-cache lib",
"keywords": [ "keywords": [
"github", "github",
@@ -38,7 +38,7 @@
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0",
"uuid": "^3.3.2" "uuid": "^3.3.2"
+5 -5
View File
@@ -8,12 +8,12 @@ import * as httpm from '@actions/http-client'
import * as semver from 'semver' import * as semver from 'semver'
import * as stream from 'stream' import * as stream from 'stream'
import * as util from 'util' import * as util from 'util'
import {ok} from 'assert'
import {OutgoingHttpHeaders} from 'http'
import uuidV4 from 'uuid/v4' import uuidV4 from 'uuid/v4'
import {exec} from '@actions/exec/lib/exec' import {exec} from '@actions/exec/lib/exec'
import {ExecOptions} from '@actions/exec/lib/interfaces' import {ExecOptions} from '@actions/exec/lib/interfaces'
import {ok} from 'assert'
import {RetryHelper} from './retry-helper' import {RetryHelper} from './retry-helper'
import {IHeaders} from '@actions/http-client/interfaces'
export class HTTPError extends Error { export class HTTPError extends Error {
constructor(readonly httpStatusCode: number | undefined) { constructor(readonly httpStatusCode: number | undefined) {
@@ -39,7 +39,7 @@ export async function downloadTool(
url: string, url: string,
dest?: string, dest?: string,
auth?: string, auth?: string,
headers?: IHeaders headers?: OutgoingHttpHeaders
): Promise<string> { ): Promise<string> {
dest = dest || path.join(_getTempDirectory(), uuidV4()) dest = dest || path.join(_getTempDirectory(), uuidV4())
await io.mkdirP(path.dirname(dest)) await io.mkdirP(path.dirname(dest))
@@ -82,7 +82,7 @@ async function downloadToolAttempt(
url: string, url: string,
dest: string, dest: string,
auth?: string, auth?: string,
headers?: IHeaders headers?: OutgoingHttpHeaders
): Promise<string> { ): Promise<string> {
if (fs.existsSync(dest)) { if (fs.existsSync(dest)) {
throw new Error(`Destination file path ${dest} already exists`) throw new Error(`Destination file path ${dest} already exists`)
@@ -596,7 +596,7 @@ export async function getManifestFromRepo(
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}` const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache') const http: httpm.HttpClient = new httpm.HttpClient('tool-cache')
const headers: IHeaders = {} const headers: OutgoingHttpHeaders = {}
if (auth) { if (auth) {
core.debug('set auth') core.debug('set auth')
headers.authorization = auth headers.authorization = auth
+2 -2
View File
@@ -9,5 +9,5 @@ if [[ -z "$name" ]]; then
exit 1 exit 1
fi fi
lerna create @actions/$name npx lerna create @actions/$name
cp packages/toolkit/tsconfig.json packages/$name/tsconfig.json cp packages/core/tsconfig.json packages/$name/tsconfig.json