Compare commits

..

24 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
+15 -15
View File
@@ -5,8 +5,8 @@ on:
inputs:
package:
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:
test:
runs-on: macos-latest
@@ -17,40 +17,40 @@ jobs:
- name: verify package exists
run: ls packages/${{ github.event.inputs.package }}
- name: Set Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: npm install
run: npm install
- name: bootstrap
run: npm run bootstrap
- name: build
run: npm run build
- name: test
run: npm run test
- name: pack
run: npm pack
working-directory: packages/${{ github.event.inputs.package }}
- name: upload artifact
uses: actions/upload-artifact@v2
with:
name: ${{ github.event.inputs.package }}
path: packages/${{ github.event.inputs.package }}/*.tgz
publish:
runs-on: macos-latest
needs: test
environment: npm-publish
steps:
- name: download artifact
uses: actions/download-artifact@v2
with:
@@ -58,7 +58,7 @@ jobs:
- name: setup authentication
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
env:
env:
NPM_TOKEN: ${{ secrets.TOKEN }}
- name: publish
@@ -68,13 +68,13 @@ jobs:
if: failure()
run: |
curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Failed to publish a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK
env:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK }}
- name: notify slack on success
if: success()
run: |
curl -X POST -H 'Content-type: application/json' --data '{"text":":dance: Successfully published a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK
env:
env:
SLACK_WEBHOOK: ${{ secrets.SLACK }}
+9
View File
@@ -46,6 +46,15 @@ $ npm install @actions/glob
```
<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)
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
- 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 configVariables from '../src/internal/config-variables'
import {retry} from '../src/internal/requestUtils'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpClientResponse} from '@actions/http-client'
jest.mock('../src/internal/config-variables')
@@ -42,7 +41,7 @@ async function testRetry(
async function handleResponse(
testResponseCode: number | undefined
): Promise<IHttpClientResponse> {
): Promise<HttpClientResponse> {
if (!testResponseCode) {
throw new Error(
'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(
statusCode: number
): Promise<IHttpClientResponse> {
): Promise<HttpClientResponse> {
const mockMessage = new http.IncomingMessage(new net.Socket())
const mockReadBody = emptyMockReadBody
mockMessage.statusCode = statusCode
+19 -2
View File
@@ -10,6 +10,7 @@ import {
getInitialRetryIntervalInMilliseconds,
getRetryMultiplier
} from '../src/internal/config-variables'
import {Readable} from 'stream'
jest.mock('../src/internal/config-variables')
@@ -74,15 +75,20 @@ describe('Utils', () => {
const size = 24
const uncompressedLength = 100
const range = 'bytes 0-199/200'
const digest = {
crc64: 'bSzITYnW/P8=',
md5: 'Xiv1fT9AxLbfadrxk2y3ZvgyN0tPwCWafL/wbi9w8mk='
}
const headers = utils.getUploadHeaders(
contentType,
true,
true,
uncompressedLength,
size,
range
range,
digest
)
expect(Object.keys(headers).length).toEqual(8)
expect(Object.keys(headers).length).toEqual(10)
expect(headers['Accept']).toEqual(
`application/json;api-version=${utils.getApiVersion()}`
)
@@ -93,6 +99,8 @@ describe('Utils', () => {
expect(headers['x-tfs-filelength']).toEqual(uncompressedLength)
expect(headers['Content-Length']).toEqual(size)
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', () => {
@@ -219,4 +227,13 @@ describe('Utils', () => {
const size2 = (await fs.promises.stat(emptyFile2)).size
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==')
})
})
+32 -14
View File
@@ -1,16 +1,16 @@
{
"name": "@actions/artifact",
"version": "0.6.1",
"version": "1.0.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/artifact",
"version": "0.6.1",
"version": "1.0.1",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/http-client": "^1.0.11",
"@actions/http-client": "^2.0.1",
"tmp": "^0.2.1",
"tmp-promise": "^3.0.2"
},
@@ -20,14 +20,14 @@
}
},
"node_modules/@actions/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"integrity": "sha512-XirM+Zo/PFlA+1h+i4bkfvagujta+LIM2AOSzPbt8JqXbbuxb1HTB+FqIyaKmue9yiCx/JIJY6pXsOl3+T8JGw==",
"dependencies": {
"@actions/http-client": "^1.0.11"
}
},
"node_modules/@actions/http-client": {
"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==",
@@ -35,6 +35,14 @@
"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": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.3.tgz",
@@ -187,19 +195,29 @@
},
"dependencies": {
"@actions/core": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"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/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==",
"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"
"tunnel": "^0.0.6"
}
},
"@types/tmp": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/artifact",
"version": "1.0.0",
"version": "1.0.2",
"preview": true,
"description": "Actions artifact lib",
"keywords": [
@@ -38,7 +38,7 @@
},
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/http-client": "^1.0.11",
"@actions/http-client": "^2.0.1",
"tmp": "^0.2.1",
"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 {performance} from 'perf_hooks'
import {ListArtifactsResponse, QueryArtifactResponse} from './contracts'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpClientResponse} from '@actions/http-client'
import {HttpManager} from './http-manager'
import {DownloadItem} from './download-specification'
import {getDownloadFileConcurrency, getRetryLimit} from './config-variables'
@@ -152,7 +152,7 @@ export class DownloadHttpClient {
const headers = getDownloadHeaders('application/json', true, true)
// 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)
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
while (retryCount <= retryLimit) {
let response: IHttpClientResponse
let response: HttpClientResponse
try {
response = await makeDownloadRequest()
} 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
*/
async pipeResponseToFile(
response: IHttpClientResponse,
response: HttpClientResponse,
destinationStream: fs.WriteStream,
isGzip: boolean
): Promise<void> {
@@ -1,4 +1,4 @@
import {HttpClient} from '@actions/http-client/index'
import {HttpClient} from '@actions/http-client'
import {createHttpClient} from './utils'
/**
@@ -1,4 +1,4 @@
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpClientResponse} from '@actions/http-client'
import {
isRetryableStatusCode,
isSuccessStatusCode,
@@ -11,11 +11,11 @@ import {getRetryLimit} from './config-variables'
export async function retry(
name: string,
operation: () => Promise<IHttpClientResponse>,
operation: () => Promise<HttpClientResponse>,
customErrorMessages: Map<number, string>,
maxAttempts: number
): Promise<IHttpClientResponse> {
let response: IHttpClientResponse | undefined = undefined
): Promise<HttpClientResponse> {
let response: HttpClientResponse | undefined = undefined
let statusCode: number | undefined = undefined
let isRetryable = false
let errorMessage = ''
@@ -71,9 +71,9 @@ export async function retry(
export async function retryHttpClientRequest(
name: string,
method: () => Promise<IHttpClientResponse>,
method: () => Promise<HttpClientResponse>,
customErrorMessages: Map<number, string> = new Map(),
maxAttempts = getRetryLimit()
): Promise<IHttpClientResponse> {
): Promise<HttpClientResponse> {
return await retry(name, method, customErrorMessages, maxAttempts)
}
@@ -9,6 +9,7 @@ import {
UploadResults
} from './contracts'
import {
digestForStream,
getArtifactUrl,
getContentRange,
getUploadHeaders,
@@ -31,8 +32,7 @@ import {promisify} from 'util'
import {URL} from 'url'
import {performance} from 'perf_hooks'
import {StatusReporter} from './status-reporter'
import {HttpCodes} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpCodes, HttpClientResponse} from '@actions/http-client'
import {HttpManager} from './http-manager'
import {UploadSpecification} from './upload-specification'
import {UploadOptions} from './upload-options'
@@ -406,6 +406,9 @@ export class UploadHttpClient {
isGzip: boolean,
totalFileSize: number
): 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
const headers = getUploadHeaders(
'application/octet-stream',
@@ -413,10 +416,11 @@ export class UploadHttpClient {
isGzip,
totalFileSize,
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)
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
// If there have been too many retries, fail so the download stops
const incrementAndCheckRetryLimit = (
response?: IHttpClientResponse
response?: HttpClientResponse
): boolean => {
retryCount++
if (retryCount > retryLimit) {
@@ -464,7 +468,7 @@ export class UploadHttpClient {
// allow for failed chunks to be retried multiple times
while (retryCount <= retryLimit) {
let response: IHttpClientResponse
let response: HttpClientResponse
try {
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 {HttpCodes, HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth'
import {IHeaders, IHttpClientResponse} from '@actions/http-client/interfaces'
import {IncomingHttpHeaders} from 'http'
import {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'
import {debug, info, warning} from '@actions/core'
import {HttpCodes, HttpClient, HttpClientResponse} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {
getRuntimeToken,
getRuntimeUrl,
@@ -11,6 +11,7 @@ import {
getRetryMultiplier,
getInitialRetryIntervalInMilliseconds
} from './config-variables'
import CRC64 from './crc64'
/**
* Returns a retry time in milliseconds that exponentially gets larger
@@ -139,8 +140,8 @@ export function getDownloadHeaders(
contentType: string,
isKeepAlive?: boolean,
acceptGzip?: boolean
): IHeaders {
const requestOptions: IHeaders = {}
): OutgoingHttpHeaders {
const requestOptions: OutgoingHttpHeaders = {}
if (contentType) {
requestOptions['Content-Type'] = contentType
@@ -180,9 +181,10 @@ export function getUploadHeaders(
isGzip?: boolean,
uncompressedLength?: number,
contentLength?: number,
contentRange?: string
): IHeaders {
const requestOptions: IHeaders = {}
contentRange?: string,
digest?: StreamDigest
): OutgoingHttpHeaders {
const requestOptions: OutgoingHttpHeaders = {}
requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`
if (contentType) {
requestOptions['Content-Type'] = contentType
@@ -202,6 +204,10 @@ export function getUploadHeaders(
if (contentRange) {
requestOptions['Content-Range'] = contentRange
}
if (digest) {
requestOptions['x-actions-results-crc64'] = digest.crc64
requestOptions['x-actions-results-md5'] = digest.md5
}
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.
* 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(
`##### Begin Diagnostic HTTP information #####
Status Code: ${response.message.statusCode}
@@ -291,3 +297,29 @@ export function getProperRetention(
export async function sleep(milliseconds: number): Promise<void> {
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
- 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 {CacheFilename, CompressionMethod} from '../src/internal/constants'
import * as tar from '../src/internal/tar'
import {ITypedResponse} from '@actions/http-client/interfaces'
import {TypedResponse} from '@actions/http-client/lib/interfaces'
import {
ReserveCacheResponse,
ITypedResponseWithError
@@ -172,7 +172,7 @@ test('save with reserve cache failure should fail', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: null,
headers: {}
@@ -208,7 +208,7 @@ test('save with server error should fail', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: {cacheId},
headers: {}
@@ -257,7 +257,7 @@ test('save with valid inputs uploads a cache', async () => {
const reserveCacheMock = jest
.spyOn(cacheHttpClient, 'reserveCache')
.mockImplementation(async () => {
const response: ITypedResponse<ReserveCacheResponse> = {
const response: TypedResponse<ReserveCacheResponse> = {
statusCode: 500,
result: {cacheId},
headers: {}
+23 -38
View File
@@ -1,18 +1,18 @@
{
"name": "@actions/cache",
"version": "2.0.2",
"version": "2.0.4",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/cache",
"version": "2.0.0",
"version": "2.0.3",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0",
"@actions/http-client": "^1.0.9",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0",
@@ -31,9 +31,9 @@
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
},
"node_modules/@actions/exec": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
@@ -48,25 +48,17 @@
}
},
"node_modules/@actions/http-client": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
"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/@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"
"tunnel": "^0.0.6"
}
},
"node_modules/@actions/io": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
},
"node_modules/@azure/abort-controller": {
"version": "1.0.4",
@@ -618,9 +610,9 @@
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
},
"@actions/exec": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
@@ -635,24 +627,17 @@
}
},
"@actions/http-client": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
"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"
},
"dependencies": {
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
}
"tunnel": "^0.0.6"
}
},
"@actions/io": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
},
"@azure/abort-controller": {
"version": "1.0.4",
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/cache",
"version": "2.0.2",
"version": "2.0.4",
"preview": true,
"description": "Actions cache lib",
"keywords": [
@@ -40,7 +40,7 @@
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0",
"@actions/http-client": "^1.0.9",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.0.1",
"@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.8.0",
@@ -48,8 +48,8 @@
"uuid": "^3.3.3"
},
"devDependencies": {
"typescript": "^3.8.3",
"@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 {HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth'
import {IRequestOptions, ITypedResponse} from '@actions/http-client/interfaces'
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {
RequestOptions,
TypedResponse
} from '@actions/http-client/lib/interfaces'
import * as crypto from 'crypto'
import * as fs from 'fs'
import {URL} from 'url'
@@ -46,8 +49,8 @@ function createAcceptHeader(type: string, apiVersion: string): string {
return `${type};api-version=${apiVersion}`
}
function getRequestOptions(): IRequestOptions {
const requestOptions: IRequestOptions = {
function getRequestOptions(): RequestOptions {
const requestOptions: RequestOptions = {
headers: {
Accept: createAcceptHeader('application/json', '6.0-preview.1')
}
@@ -275,7 +278,7 @@ async function commitCache(
httpClient: HttpClient,
cacheId: number,
filesize: number
): Promise<ITypedResponse<null>> {
): Promise<TypedResponse<null>> {
const commitCacheRequest: CommitCacheRequest = {size: filesize}
return await retryTypedResponse('commitCache', async () =>
httpClient.postJson<null>(
+2 -2
View File
@@ -1,8 +1,8 @@
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'
export interface ITypedResponseWithError<T> extends ITypedResponse<T> {
export interface ITypedResponseWithError<T> extends TypedResponse<T> {
error?: HttpClientError
}
+2 -3
View File
@@ -1,6 +1,5 @@
import * as core from '@actions/core'
import {HttpClient} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {HttpClient, HttpClientResponse} from '@actions/http-client'
import {BlockBlobClient} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/ms-rest-js'
import * as buffer from 'buffer'
@@ -20,7 +19,7 @@ import {retryHttpClientResponse} from './requestUtils'
* @param output the writable stream
*/
async function pipeResponseToStream(
response: IHttpClientResponse,
response: HttpClientResponse,
output: NodeJS.WritableStream
): Promise<void> {
const pipeline = util.promisify(stream.pipeline)
+9 -6
View File
@@ -1,6 +1,9 @@
import * as core from '@actions/core'
import {HttpCodes, HttpClientError} from '@actions/http-client'
import {IHttpClientResponse} from '@actions/http-client/interfaces'
import {
HttpCodes,
HttpClientError,
HttpClientResponse
} from '@actions/http-client'
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
import {ITypedResponseWithError} from './contracts'
@@ -103,7 +106,7 @@ export async function retryTypedResponse<T>(
maxAttempts,
delay,
// 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) => {
if (error instanceof HttpClientError) {
return {
@@ -121,14 +124,14 @@ export async function retryTypedResponse<T>(
export async function retryHttpClientResponse(
name: string,
method: () => Promise<IHttpClientResponse>,
method: () => Promise<HttpClientResponse>,
maxAttempts = DefaultRetryAttempts,
delay = DefaultRetryDelay
): Promise<IHttpClientResponse> {
): Promise<HttpClientResponse> {
return await retry(
name,
method,
(response: IHttpClientResponse) => response.message.statusCode,
(response: HttpClientResponse) => response.message.statusCode,
maxAttempts,
delay
)
+11
View File
@@ -1,5 +1,16 @@
# @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
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
@@ -1,9 +1,10 @@
import * as fs from 'fs'
import * as os from 'os'
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> {
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 () => {
process.env[SUMMARY_ENV_VAR] = testFilePath
await fs.promises.mkdir(testDirectoryPath, {recursive: true})
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
markdownSummary.emptyBuffer()
summary.emptyBuffer()
})
afterAll(async () => {
@@ -80,39 +82,39 @@ describe('@actions/core/src/markdown-summary', () => {
it('throws if summary env var is undefined', async () => {
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()
})
it('throws if summary file does not exist', async () => {
await fs.promises.unlink(testFilePath)
const write = markdownSummary.addRaw(fixtures.text).write()
const write = summary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow()
})
it('appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write()
await summary.addRaw(fixtures.text).write()
await assertSummary(`# ${fixtures.text}`)
})
it('overwrites text to summary file', async () => {
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write({overwrite: true})
await summary.addRaw(fixtures.text).write({overwrite: true})
await assertSummary(fixtures.text)
})
it('appends text with EOL to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text, true).write()
await summary.addRaw(fixtures.text, true).write()
await assertSummary(`# ${fixtures.text}${os.EOL}`)
})
it('chains appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary
await summary
.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 () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write()
await summary.addRaw(fixtures.text).write()
await assertSummary(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(true)
expect(summary.isEmptyBuffer()).toBe(true)
})
it('returns summary buffer as string', () => {
markdownSummary.addRaw(fixtures.text)
expect(markdownSummary.stringify()).toEqual(fixtures.text)
summary.addRaw(fixtures.text)
expect(summary.stringify()).toEqual(fixtures.text)
})
it('return correct values for isEmptyBuffer', () => {
markdownSummary.addRaw(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(false)
summary.addRaw(fixtures.text)
expect(summary.isEmptyBuffer()).toBe(false)
markdownSummary.emptyBuffer()
expect(markdownSummary.isEmptyBuffer()).toBe(true)
summary.emptyBuffer()
expect(summary.isEmptyBuffer()).toBe(true)
})
it('clears a buffer and summary file', async () => {
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
await markdownSummary.clear()
await summary.clear()
await assertSummary('')
expect(markdownSummary.isEmptyBuffer()).toBe(true)
expect(summary.isEmptyBuffer()).toBe(true)
})
it('adds EOL', async () => {
await markdownSummary
await summary
.addRaw(fixtures.text)
.addEOL()
.write()
@@ -156,37 +158,37 @@ describe('@actions/core/src/markdown-summary', () => {
})
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}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
it('adds a details element', async () => {
await markdownSummary
await summary
.addDetails(fixtures.details.label, fixtures.details.content)
.write()
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 () => {
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}`
await assertSummary(expected)
})
it('adds an image with custom dimensions', async () => {
await markdownSummary
await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -208,7 +210,7 @@ describe('@actions/core/src/markdown-summary', () => {
})
it('adds an image with custom dimensions', async () => {
await markdownSummary
await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -217,21 +219,21 @@ describe('@actions/core/src/markdown-summary', () => {
it('adds headings h1...h6', async () => {
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}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
it('uses h1 if heading level is garbage or out of range', async () => {
await markdownSummary
await summary
.addHeading('heading', 'foobar')
.addHeading('heading', 1337)
.addHeading('heading', -1)
@@ -242,35 +244,31 @@ describe('@actions/core/src/markdown-summary', () => {
})
it('adds a separator', async () => {
await markdownSummary.addSeparator().write()
await summary.addSeparator().write()
const expected = `<hr>${os.EOL}`
await assertSummary(expected)
})
it('adds a break', async () => {
await markdownSummary.addBreak().write()
await summary.addBreak().write()
const expected = `<br>${os.EOL}`
await assertSummary(expected)
})
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}`
await assertSummary(expected)
})
it('adds a quote with citation', async () => {
await markdownSummary
.addQuote(fixtures.quote.text, fixtures.quote.cite)
.write()
await summary.addQuote(fixtures.quote.text, fixtures.quote.cite).write()
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected)
})
it('adds a link with href', async () => {
await markdownSummary
.addLink(fixtures.link.text, fixtures.link.href)
.write()
await summary.addLink(fixtures.link.text, fixtures.link.href).write()
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
await assertSummary(expected)
})
+11 -11
View File
@@ -1,26 +1,26 @@
{
"name": "@actions/core",
"version": "1.7.0",
"version": "1.8.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/core",
"version": "1.6.0",
"version": "1.8.1",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^1.0.11"
"@actions/http-client": "^2.0.1"
},
"devDependencies": {
"@types/node": "^12.0.2"
}
},
"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==",
"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"
"tunnel": "^0.0.6"
}
},
"node_modules/@types/node": {
@@ -40,11 +40,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==",
"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"
"tunnel": "^0.0.6"
}
},
"@types/node": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "1.7.0",
"version": "1.8.2",
"description": "Actions core lib",
"keywords": [
"github",
@@ -36,7 +36,7 @@
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/http-client": "^1.0.11"
"@actions/http-client": "^2.0.1"
},
"devDependencies": {
"@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 */
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 {BearerCredentialHandler} from '@actions/http-client/auth'
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
import {debug, setSecret} from './core'
interface TokenResponse {
value?: string
@@ -13,7 +13,7 @@ export class OidcClient {
allowRetry = true,
maxRetry = 10
): actions_http_client.HttpClient {
const requestOptions: IRequestOptions = {
const requestOptions: RequestOptions = {
allowRetries: allowRetry,
maxRetries: maxRetry
}
@@ -4,7 +4,7 @@ const {access, appendFile, writeFile} = promises
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
export const SUMMARY_DOCS_URL =
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary'
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'
export type SummaryTableRow = (SummaryTableCell | string)[]
@@ -51,7 +51,7 @@ export interface SummaryWriteOptions {
overwrite?: boolean
}
class MarkdownSummary {
class Summary {
private _buffer: string
private _filePath?: string
@@ -73,7 +73,7 @@ class MarkdownSummary {
const pathFromEnv = process.env[SUMMARY_ENV_VAR]
if (!pathFromEnv) {
throw new Error(
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`
`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
*
* @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 filePath = await this.filePath()
const writeFunc = overwrite ? writeFile : appendFile
@@ -132,9 +132,9 @@ class MarkdownSummary {
/**
* 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})
}
@@ -159,9 +159,9 @@ class MarkdownSummary {
/**
* Resets the summary buffer without writing to summary file
*
* @returns {MarkdownSummary} markdown summary instance
* @returns {Summary} summary instance
*/
emptyBuffer(): MarkdownSummary {
emptyBuffer(): Summary {
this._buffer = ''
return this
}
@@ -172,9 +172,9 @@ class MarkdownSummary {
* @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
*
* @returns {MarkdownSummary} markdown summary instance
* @returns {Summary} summary instance
*/
addRaw(text: string, addEOL = false): MarkdownSummary {
addRaw(text: string, addEOL = false): Summary {
this._buffer += text
return addEOL ? this.addEOL() : this
}
@@ -182,9 +182,9 @@ class MarkdownSummary {
/**
* 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)
}
@@ -194,9 +194,9 @@ class MarkdownSummary {
* @param {string} code content to render within fenced code block
* @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 = {
...(lang && {lang})
}
@@ -210,9 +210,9 @@ class MarkdownSummary {
* @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
*
* @returns {MarkdownSummary} markdown summary instance
* @returns {Summary} summary instance
*/
addList(items: string[], ordered = false): MarkdownSummary {
addList(items: string[], ordered = false): Summary {
const tag = ordered ? 'ol' : 'ul'
const listItems = items.map(item => this.wrap('li', item)).join('')
const element = this.wrap(tag, listItems)
@@ -224,9 +224,9 @@ class MarkdownSummary {
*
* @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
.map(row => {
const cells = row
@@ -260,9 +260,9 @@ class MarkdownSummary {
* @param {string} label text for the closed state
* @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)
return this.addRaw(element).addEOL()
}
@@ -274,13 +274,9 @@ class MarkdownSummary {
* @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes
*
* @returns {MarkdownSummary} markdown summary instance
* @returns {Summary} summary instance
*/
addImage(
src: string,
alt: string,
options?: SummaryImageOptions
): MarkdownSummary {
addImage(src: string, alt: string, options?: SummaryImageOptions): Summary {
const {width, height} = options || {}
const attrs = {
...(width && {width}),
@@ -297,9 +293,9 @@ class MarkdownSummary {
* @param {string} text heading text
* @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 allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
? tag
@@ -311,9 +307,9 @@ class MarkdownSummary {
/**
* 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)
return this.addRaw(element).addEOL()
}
@@ -321,9 +317,9 @@ class MarkdownSummary {
/**
* 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)
return this.addRaw(element).addEOL()
}
@@ -334,9 +330,9 @@ class MarkdownSummary {
* @param {string} text quote text
* @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 = {
...(cite && {cite})
}
@@ -350,13 +346,18 @@ class MarkdownSummary {
* @param {string} text link text/content
* @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})
return this.addRaw(element).addEOL()
}
}
// singleton export
export const markdownSummary = new MarkdownSummary()
const _summary = new Summary()
/**
* @deprecated use `core.summary`
*/
export const markdownSummary = _summary
export const summary = _summary
+6
View File
@@ -1,5 +1,11 @@
# @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
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
### 5.0.0
+11 -11
View File
@@ -1,15 +1,15 @@
{
"name": "@actions/github",
"version": "5.0.1",
"version": "5.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/github",
"version": "5.0.1",
"version": "5.0.2",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^1.0.11",
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
@@ -19,11 +19,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==",
"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"
"tunnel": "^0.0.6"
}
},
"node_modules/@octokit/auth-token": {
@@ -361,11 +361,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==",
"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"
"tunnel": "^0.0.6"
}
},
"@octokit/auth-token": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/github",
"version": "5.0.1",
"version": "5.0.3",
"description": "Actions github lib",
"keywords": [
"github",
@@ -38,7 +38,7 @@
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/http-client": "^1.0.11",
"@actions/http-client": "^2.0.1",
"@octokit/core": "^3.6.0",
"@octokit/plugin-paginate-rest": "^2.17.0",
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
+42 -7
View File
@@ -6,7 +6,7 @@
"packages": {
"": {
"name": "@actions/glob",
"version": "0.2.1",
"version": "0.3.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
@@ -14,9 +14,20 @@
}
},
"node_modules/@actions/core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"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": {
"version": "1.0.0",
@@ -47,13 +58,32 @@
"engines": {
"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": {
"@actions/core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"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": {
"version": "1.0.0",
@@ -81,6 +111,11 @@
"requires": {
"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
npm-debug.log
+13 -19
View File
@@ -1,18 +1,11 @@
# `@actions/http-client`
<p align="center">
<img src="actions.png">
</p>
# Actions Http-Client
[![Http Status](https://github.com/actions/http-client/workflows/http-tests/badge.svg)](https://github.com/actions/http-client/actions)
A lightweight HTTP client optimized for use with actions, TypeScript with generics and async await.
A lightweight HTTP client optimized for building actions.
## Features
- HTTP client with TypeScript generics and async/await/Promises
- Typings included so no need to acquire separately (great for intellisense and no versioning drift)
- Typings included!
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner
- Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+.
- Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
@@ -28,7 +21,7 @@ npm install @actions/http-client --save
## Samples
See the [HTTP](./__tests__) tests for detailed examples.
See the [tests](./__tests__) for detailed examples.
## 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.
* Redirects (3xx) will be followed by default.
See [HTTP tests](./__tests__) for detailed examples.
See the [tests](./__tests__) for detailed examples.
## Debugging
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
```
```shell
export NODE_DEBUG=http
```
@@ -63,17 +56,18 @@ We welcome PRs. Please create an issue and if applicable, a design before proce
once:
```bash
$ npm install
```
npm install
```
To build:
```bash
$ npm run build
```
npm run build
```
To run all tests:
```bash
$ npm test
```
npm test
```
+13
View File
@@ -1,5 +1,18 @@
## Releases
## 2.0.1
- Fix an issue with missing `tunnel` dependency [#1085](https://github.com/actions/toolkit/pull/1085)
## 2.0.0
- The package is now compiled with TypeScript's [`strict` compiler setting](https://www.typescriptlang.org/tsconfig#strict). To comply with stricter rules:
- Some exported types now include `| null` or `| undefined`, matching their actual behavior.
- Types implementing the method `RequestHandler.handleAuthentication()` now throw an `Error` rather than returning `null` if they do not support handling an HTTP 401 response. Callers can still use `canHandleAuthentication()` to determine if this handling is supported or not.
- Types using `any` have been scoped to more specific types.
- Following TypeScript's naming conventions, exported interfaces no longer begin with the prefix `I-`.
- Delete the `IHttpClientResponse` interface in favor of the `HttpClientResponse` class.
- Delete the `IHeaders` interface in favor of `http.OutgoingHttpHeaders`.
- The source code of the package was moved to build with [actions/toolkit](https://github.com/actions/toolkit).
## 1.0.11
Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
+38 -26
View File
@@ -1,5 +1,5 @@
import * as httpm from '../_out'
import * as am from '../_out/auth'
import * as httpm from '../lib'
import * as am from '../lib/auth'
describe('auth', () => {
beforeEach(() => {})
@@ -7,17 +7,21 @@ describe('auth', () => {
afterEach(() => {})
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',
'password'
)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
bh
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
let creds: string = Buffer.from(
const body: string = await res.readBody()
const obj = JSON.parse(body)
const auth: string = obj.headers.Authorization
const creds: string = Buffer.from(
auth.substring('Basic '.length),
'base64'
).toString()
@@ -26,36 +30,44 @@ describe('auth', () => {
})
it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
const ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
token
)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
let creds: string = Buffer.from(
const body: string = await res.readBody()
const obj = JSON.parse(body)
const auth: string = obj.headers.Authorization
const creds: string = Buffer.from(
auth.substring('Basic '.length),
'base64'
).toString()
expect(creds).toBe('PAT:' + token)
expect(creds).toBe(`PAT:${token}`)
expect(obj.url).toBe('http://httpbin.org/get')
})
it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph])
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
let auth: string = obj.headers.Authorization
expect(auth).toBe('Bearer ' + token)
const body: string = await res.readBody()
const obj = JSON.parse(body)
const auth: string = obj.headers.Authorization
expect(auth).toBe(`Bearer ${token}`)
expect(obj.url).toBe('http://httpbin.org/get')
})
})
+152 -153
View File
@@ -1,9 +1,10 @@
import * as httpm from '../_out'
import * as ifm from '../_out/interfaces'
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as httpm from '..'
import * as path from 'path'
import * as fs from 'fs'
let sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
const sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
interface HttpBinData {
url: string
@@ -23,7 +24,7 @@ describe('basics', () => {
afterEach(() => {})
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()
})
@@ -39,264 +40,259 @@ describe('basics', () => {
// "url": "https://httpbin.org/get"
// }
it('does basic http get request', async done => {
let res: httpm.HttpClientResponse = await _http.get(
it('does basic http get request', async () => {
const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeTruthy()
done()
})
it('does basic http get request with no user agent', async done => {
let http: httpm.HttpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get')
it('does basic http get request with no user agent', async () => {
const http: httpm.HttpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeFalsy()
done()
})
it('does basic https get request', async done => {
let res: httpm.HttpClientResponse = await _http.get(
it('does basic https get request', async () => {
const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('does basic http get request with default headers', async done => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
it('does basic http get request with default headers', async () => {
const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
}
})
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)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe('application/json')
expect(obj.url).toBe('http://httpbin.org/get')
done()
})
it('does basic http get request with merged headers', async done => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], {
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
it('does basic http get request with merged headers', async () => {
const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: {
Accept: 'application/json',
'Content-Type': 'application/json'
}
}
})
let res: httpm.HttpClientResponse = await http.get(
)
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get',
{
'content-type': 'application/x-www-form-urlencoded'
}
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe(
'application/x-www-form-urlencoded'
)
expect(obj.url).toBe('http://httpbin.org/get')
done()
})
it('pipes a get request', () => {
return new Promise<string>(async (resolve, reject) => {
let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath)
it('pipes a get request', async () => {
return new Promise<void>(async resolve => {
const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://httpbin.org/get')).message
.pipe(file)
.on('close', () => {
let body: string = fs.readFileSync(sampleFilePath).toString()
let obj: any = JSON.parse(body)
const body: string = fs.readFileSync(sampleFilePath).toString()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
resolve()
})
})
})
it('does basic get request with redirects', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/get')
it('does basic get request with redirects', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://httpbin.org/get'
)}`
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('does basic get request with redirects (303)', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/get') +
'&status_code=303'
it('does basic get request with redirects (303)', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://httpbin.org/get'
)}&status_code=303`
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
done()
})
it('returns 404 for not found get request on redirect', async done => {
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/status/404') +
'&status_code=303'
it('returns 404 for not found get request on redirect', async () => {
const res: httpm.HttpClientResponse = await _http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://httpbin.org/status/404'
)}&status_code=303`
)
expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody()
done()
await res.readBody()
})
it('does not follow redirects if disabled', async done => {
let http: httpm.HttpClient = new httpm.HttpClient(
it('does not follow redirects if disabled', async () => {
const http: httpm.HttpClient = new httpm.HttpClient(
'typed-test-client-tests',
null,
undefined,
{allowRedirects: false}
)
let res: httpm.HttpClientResponse = await http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://httpbin.org/get')
const res: httpm.HttpClientResponse = await http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://httpbin.org/get'
)}`
)
expect(res.message.statusCode).toBe(302)
let body: string = await res.readBody()
done()
await res.readBody()
})
it('does not pass auth with diff hostname redirects', async done => {
let headers = {
it('does not pass auth with diff hostname redirects', async () => {
const headers = {
accept: 'application/json',
authorization: 'shhh'
}
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://www.httpbin.org/get'),
const res: httpm.HttpClientResponse = await _http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://www.httpbin.org/get'
)}`,
headers
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
// httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get')
done()
})
it('does not pass Auth with diff hostname redirects', async done => {
let headers = {
it('does not pass Auth with diff hostname redirects', async () => {
const headers = {
Accept: 'application/json',
Authorization: 'shhh'
}
let res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' +
encodeURIComponent('https://www.httpbin.org/get'),
const res: httpm.HttpClientResponse = await _http.get(
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
'https://www.httpbin.org/get'
)}`,
headers
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
// httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get')
done()
})
it('does basic head request', async done => {
let res: httpm.HttpClientResponse = await _http.head(
it('does basic head request', async () => {
const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
done()
})
it('does basic http delete request', async done => {
let res: httpm.HttpClientResponse = await _http.del(
it('does basic http delete request', async () => {
const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
done()
const body: string = await res.readBody()
JSON.parse(body)
})
it('does basic http post request', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post(
it('does basic http post request', async () => {
const b = 'Hello World!'
const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post')
done()
})
it('does basic http patch request', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch(
it('does basic http patch request', async () => {
const b = 'Hello World!'
const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch')
done()
})
it('does basic http options request', async done => {
let res: httpm.HttpClientResponse = await _http.options(
it('does basic http options request', async () => {
const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
done()
await res.readBody()
})
it('returns 404 for not found get request', async done => {
let res: httpm.HttpClientResponse = await _http.get(
it('returns 404 for not found get request', async () => {
const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/status/404'
)
expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody()
done()
await res.readBody()
})
it('gets a json object', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson<
HttpBinData
>('https://httpbin.org/get')
const jsonObj = await _http.getJson<HttpBinData>('https://httpbin.org/get')
expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined()
expect(jsonObj.result.url).toBe('https://httpbin.org/get')
expect(jsonObj.result.headers['Accept']).toBe(
expect(jsonObj.result?.url).toBe('https://httpbin.org/get')
expect(jsonObj.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
@@ -305,26 +301,27 @@ describe('basics', () => {
})
it('getting a non existent json object returns null', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson<
HttpBinData
>('https://httpbin.org/status/404')
const jsonObj = await _http.getJson<HttpBinData>(
'https://httpbin.org/status/404'
)
expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull()
})
it('posts a json object', async () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.postJson<
HttpBinData
>('https://httpbin.org/post', res)
const res = {name: 'foo'}
const restRes = await _http.postJson<HttpBinData>(
'https://httpbin.org/post',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/post')
expect(restRes.result.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe(
expect(restRes.result?.url).toBe('https://httpbin.org/post')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.result.headers['Content-Type']).toBe(
expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -333,19 +330,20 @@ describe('basics', () => {
})
it('puts a json object', async () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.putJson<
HttpBinData
>('https://httpbin.org/put', res)
const res = {name: 'foo'}
const restRes = await _http.putJson<HttpBinData>(
'https://httpbin.org/put',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/put')
expect(restRes.result.json.name).toBe('foo')
expect(restRes.result?.url).toBe('https://httpbin.org/put')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe(
expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.result.headers['Content-Type']).toBe(
expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -354,18 +352,19 @@ describe('basics', () => {
})
it('patch a json object', async () => {
let res: any = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.patchJson<
HttpBinData
>('https://httpbin.org/patch', res)
const res = {name: 'foo'}
const restRes = await _http.patchJson<HttpBinData>(
'https://httpbin.org/patch',
res
)
expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/patch')
expect(restRes.result.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe(
expect(restRes.result?.url).toBe('https://httpbin.org/patch')
expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.result.headers['Content-Type']).toBe(
expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson
)
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
+15 -14
View File
@@ -1,5 +1,6 @@
import * as httpm from '../_out'
import * as ifm from '../_out/interfaces'
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as httpm from '..'
describe('headers', () => {
let _http: httpm.HttpClient
@@ -9,8 +10,8 @@ describe('headers', () => {
})
it('preserves existing headers on getJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.getJson<any>(
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.getJson<any>(
'https://httpbin.org/get',
additionalHeaders
)
@@ -19,7 +20,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson
)
let httpWithHeaders = new httpm.HttpClient()
const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = {
headers: {
[httpm.Headers.Accept]: 'baz'
@@ -33,8 +34,8 @@ describe('headers', () => {
})
it('preserves existing headers on postJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.postJson<any>(
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.postJson<any>(
'https://httpbin.org/post',
{},
additionalHeaders
@@ -44,7 +45,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson
)
let httpWithHeaders = new httpm.HttpClient()
const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = {
headers: {
[httpm.Headers.Accept]: 'baz'
@@ -61,8 +62,8 @@ describe('headers', () => {
})
it('preserves existing headers on putJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.putJson<any>(
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.putJson<any>(
'https://httpbin.org/put',
{},
additionalHeaders
@@ -72,7 +73,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson
)
let httpWithHeaders = new httpm.HttpClient()
const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = {
headers: {
[httpm.Headers.Accept]: 'baz'
@@ -86,8 +87,8 @@ describe('headers', () => {
})
it('preserves existing headers on patchJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.patchJson<any>(
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.patchJson<any>(
'https://httpbin.org/patch',
{},
additionalHeaders
@@ -97,7 +98,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson
)
let httpWithHeaders = new httpm.HttpClient()
const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = {
headers: {
[httpm.Headers.Accept]: 'baz'
@@ -1,4 +1,4 @@
import * as httpm from '../_out'
import * as httpm from '../lib'
describe('basics', () => {
let _http: httpm.HttpClient
@@ -11,69 +11,63 @@ describe('basics', () => {
_http.dispose()
})
it('does basic http get request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.get(
it('does basic http get request with keepAlive true', async () => {
const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
done()
})
it('does basic head request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.head(
it('does basic head request with keepAlive true', async () => {
const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
done()
})
it('does basic http delete request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.del(
it('does basic http delete request with keepAlive true', async () => {
const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
done()
const body: string = await res.readBody()
JSON.parse(body)
})
it('does basic http post request with keepAlive true', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post(
it('does basic http post request with keepAlive true', async () => {
const b = 'Hello World!'
const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post')
done()
})
it('does basic http patch request with keepAlive true', async done => {
let b: string = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch(
it('does basic http patch request with keepAlive true', async () => {
const b = 'Hello World!'
const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch',
b
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch')
done()
})
it('does basic http options request with keepAlive true', async done => {
let res: httpm.HttpClientResponse = await _http.options(
it('does basic http options request with keepAlive true', async () => {
const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
done()
await res.readBody()
})
})
+43 -39
View File
@@ -1,18 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as http from 'http'
import * as httpm from '../_out'
import * as pm from '../_out/proxy'
import * as proxy from 'proxy'
import * as tunnelm from 'tunnel'
import * as httpm from '../lib/'
import * as pm from '../lib/proxy'
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
const proxy = require('proxy')
let _proxyConnects: string[]
let _proxyServer: http.Server
let _proxyUrl = 'http://127.0.0.1:8080'
const _proxyUrl = 'http://127.0.0.1:8080'
describe('proxy', () => {
beforeAll(async () => {
// Start proxy server
_proxyServer = proxy()
await new Promise(resolve => {
await new Promise<void>(resolve => {
const port = Number(_proxyUrl.split(':')[2])
_proxyServer.listen(port, () => resolve())
})
@@ -32,126 +34,126 @@ describe('proxy', () => {
_clearVars()
// Stop proxy server
await new Promise(resolve => {
await new Promise<void>(resolve => {
_proxyServer.once('close', () => resolve())
_proxyServer.close()
})
})
it('getProxyUrl does not return proxyUrl if variables not set', () => {
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined()
})
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
process.env['https_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined()
})
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
process.env['http_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined()
})
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
process.env['http_proxy'] = 'http://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined()
})
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
const proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
expect(proxyUrl).toBeUndefined()
})
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined()
})
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
const proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
expect(proxyUrl).toBeUndefined()
})
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined()
})
it('checkBypass returns true if host as no_proxy list', () => {
process.env['no_proxy'] = 'myserver'
let bypass = pm.checkBypass(new URL('https://myserver'))
const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver'))
const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host in no_proxy list with spaces', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver'))
const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host in no_proxy list with port', () => {
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080'))
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host with port in no_proxy list without port', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080'))
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host in no_proxy list with default https port', () => {
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver'))
const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns true if host in no_proxy list with default http port', () => {
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
let bypass = pm.checkBypass(new URL('http://myserver'))
const bypass = pm.checkBypass(new URL('http://myserver'))
expect(bypass).toBeTruthy()
})
it('checkBypass returns false if host not in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://github.com'))
const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy()
})
it('checkBypass returns false if empty no_proxy', () => {
process.env['no_proxy'] = ''
let bypass = pm.checkBypass(new URL('https://github.com'))
const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy()
})
it('HttpClient does basic http get request through proxy', async () => {
process.env['http_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get(
const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:80'])
})
@@ -160,12 +162,12 @@ describe('proxy', () => {
process.env['http_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get(
const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0)
})
@@ -173,12 +175,12 @@ describe('proxy', () => {
it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get(
const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:443'])
})
@@ -187,12 +189,12 @@ describe('proxy', () => {
process.env['https_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get(
const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody()
let obj: any = JSON.parse(body)
const body: string = await res.readBody()
const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0)
})
@@ -200,7 +202,8 @@ describe('proxy', () => {
it('proxyAuth not set in tunnel agent when authentication is not provided', async () => {
process.env['https_proxy'] = 'http://127.0.0.1:8080'
const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url')
const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080')
@@ -210,7 +213,8 @@ describe('proxy', () => {
it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
process.env['https_proxy'] = 'http://user:password@127.0.0.1:8080'
const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url')
const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080')
@@ -218,7 +222,7 @@ describe('proxy', () => {
})
})
function _clearVars() {
function _clearVars(): void {
delete process.env.http_proxy
delete process.env.HTTP_PROXY
delete process.env.https_proxy
+114 -10276
View File
File diff suppressed because it is too large Load Diff
+34 -25
View File
@@ -1,39 +1,48 @@
{
"name": "@actions/http-client",
"version": "1.0.11",
"version": "2.0.1",
"description": "Actions Http Client",
"main": "index.js",
"scripts": {
"build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out",
"test": "jest",
"format": "prettier --write *.ts && prettier --write **/*.ts",
"format-check": "prettier --check *.ts && prettier --check **/*.ts",
"audit-check": "npm audit --audit-level=moderate"
"keywords": [
"github",
"actions",
"http"
],
"homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
"license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"!.DS_Store"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/actions/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": {
"url": "https://github.com/actions/http-client/issues"
"url": "https://github.com/actions/toolkit/issues"
},
"homepage": "https://github.com/actions/http-client#readme",
"devDependencies": {
"@types/jest": "^25.1.4",
"@types/node": "^12.12.31",
"jest": "^25.1.0",
"prettier": "^2.0.4",
"proxy": "^1.0.1",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
"@types/tunnel": "0.0.3",
"proxy": "^1.0.1"
},
"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
password: string
@@ -9,27 +11,26 @@ export class BasicCredentialHandler implements ifm.IRequestHandler {
this.password = password
}
prepareRequest(options: any): void {
options.headers['Authorization'] =
'Basic ' +
Buffer.from(this.username + ':' + this.password).toString('base64')
prepareRequest(options: http.RequestOptions): void {
if (!options.headers) {
throw Error('The request has no headers')
}
options.headers['Authorization'] = `Basic ${Buffer.from(
`${this.username}:${this.password}`
).toString('base64')}`
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
canHandleAuthentication(): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
async handleAuthentication(): Promise<HttpClientResponse> {
throw new Error('not implemented')
}
}
export class BearerCredentialHandler implements ifm.IRequestHandler {
export class BearerCredentialHandler implements ifm.RequestHandler {
token: string
constructor(token: string) {
@@ -38,26 +39,25 @@ export class BearerCredentialHandler implements ifm.IRequestHandler {
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void {
options.headers['Authorization'] = 'Bearer ' + this.token
prepareRequest(options: http.RequestOptions): void {
if (!options.headers) {
throw Error('The request has no headers')
}
options.headers['Authorization'] = `Bearer ${this.token}`
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
canHandleAuthentication(): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
async handleAuthentication(): Promise<HttpClientResponse> {
throw new Error('not implemented')
}
}
export class PersonalAccessTokenCredentialHandler
implements ifm.IRequestHandler {
implements ifm.RequestHandler {
token: string
constructor(token: string) {
@@ -66,21 +66,21 @@ export class PersonalAccessTokenCredentialHandler
// currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void {
options.headers['Authorization'] =
'Basic ' + Buffer.from('PAT:' + this.token).toString('base64')
prepareRequest(options: http.RequestOptions): void {
if (!options.headers) {
throw Error('The request has no headers')
}
options.headers['Authorization'] = `Basic ${Buffer.from(
`PAT:${this.token}`
).toString('base64')}`
}
// This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean {
canHandleAuthentication(): boolean {
return false
}
handleAuthentication(
httpClient: ifm.IHttpClient,
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
async handleAuthentication(): Promise<HttpClientResponse> {
throw new Error('not implemented')
}
}
+183 -178
View File
@@ -1,9 +1,11 @@
import http = require('http')
import https = require('https')
import ifm = require('./interfaces')
import pm = require('./proxy')
/* eslint-disable @typescript-eslint/no-explicit-any */
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 {
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
*/
export function getProxyUrl(serverUrl: string): string {
let proxyUrl = pm.getProxyUrl(new URL(serverUrl))
const proxyUrl = pm.getProxyUrl(new URL(serverUrl))
return proxyUrl ? proxyUrl.href : ''
}
@@ -77,18 +79,18 @@ export class HttpClientError extends Error {
Object.setPrototypeOf(this, HttpClientError.prototype)
}
public statusCode: number
public result?: any
statusCode: number
result?: any
}
export class HttpClientResponse implements ifm.IHttpClientResponse {
export class HttpClientResponse {
constructor(message: http.IncomingMessage) {
this.message = message
}
public message: http.IncomingMessage
readBody(): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
message: http.IncomingMessage
async readBody(): Promise<string> {
return new Promise<string>(async resolve => {
let output = Buffer.alloc(0)
this.message.on('data', (chunk: Buffer) => {
@@ -102,32 +104,32 @@ export class HttpClientResponse implements ifm.IHttpClientResponse {
}
}
export function isHttps(requestUrl: string) {
let parsedUrl: URL = new URL(requestUrl)
export function isHttps(requestUrl: string): boolean {
const parsedUrl: URL = new URL(requestUrl)
return parsedUrl.protocol === 'https:'
}
export class HttpClient {
userAgent: string | undefined
handlers: ifm.IRequestHandler[]
requestOptions: ifm.IRequestOptions
handlers: ifm.RequestHandler[]
requestOptions: ifm.RequestOptions | undefined
private _ignoreSslError: boolean = false
private _socketTimeout: number
private _allowRedirects: boolean = true
private _allowRedirectDowngrade: boolean = false
private _maxRedirects: number = 50
private _allowRetries: boolean = false
private _maxRetries: number = 1
private _agent
private _proxyAgent
private _keepAlive: boolean = false
private _disposed: boolean = false
private _ignoreSslError = false
private _socketTimeout: number | undefined
private _allowRedirects = true
private _allowRedirectDowngrade = false
private _maxRedirects = 50
private _allowRetries = false
private _maxRetries = 1
private _agent: any
private _proxyAgent: any
private _keepAlive = false
private _disposed = false
constructor(
userAgent?: string,
handlers?: ifm.IRequestHandler[],
requestOptions?: ifm.IRequestOptions
handlers?: ifm.RequestHandler[],
requestOptions?: ifm.RequestOptions
) {
this.userAgent = userAgent
this.handlers = handlers || []
@@ -165,64 +167,64 @@ export class HttpClient {
}
}
public options(
async options(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
}
public get(
async get(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('GET', requestUrl, null, additionalHeaders || {})
}
public del(
async del(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('DELETE', requestUrl, null, additionalHeaders || {})
}
public post(
async post(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('POST', requestUrl, data, additionalHeaders || {})
}
public patch(
async patch(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('PATCH', requestUrl, data, additionalHeaders || {})
}
public put(
async put(
requestUrl: string,
data: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('PUT', requestUrl, data, additionalHeaders || {})
}
public head(
async head(
requestUrl: string,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request('HEAD', requestUrl, null, additionalHeaders || {})
}
public sendStream(
async sendStream(
verb: string,
requestUrl: string,
stream: NodeJS.ReadableStream,
additionalHeaders?: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
return this.request(verb, requestUrl, stream, additionalHeaders)
}
@@ -230,28 +232,28 @@ export class HttpClient {
* Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/
public async getJson<T>(
async getJson<T>(
requestUrl: string,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.TypedResponse<T>> {
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders,
Headers.Accept,
MediaTypes.ApplicationJson
)
let res: ifm.IHttpClientResponse = await this.get(
const res: HttpClientResponse = await this.get(
requestUrl,
additionalHeaders
)
return this._processResponse<T>(res, this.requestOptions)
}
public async postJson<T>(
async postJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2)
additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.TypedResponse<T>> {
const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders,
Headers.Accept,
@@ -262,7 +264,7 @@ export class HttpClient {
Headers.ContentType,
MediaTypes.ApplicationJson
)
let res: ifm.IHttpClientResponse = await this.post(
const res: HttpClientResponse = await this.post(
requestUrl,
data,
additionalHeaders
@@ -270,12 +272,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions)
}
public async putJson<T>(
async putJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2)
additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.TypedResponse<T>> {
const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders,
Headers.Accept,
@@ -286,7 +288,7 @@ export class HttpClient {
Headers.ContentType,
MediaTypes.ApplicationJson
)
let res: ifm.IHttpClientResponse = await this.put(
const res: HttpClientResponse = await this.put(
requestUrl,
data,
additionalHeaders
@@ -294,12 +296,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions)
}
public async patchJson<T>(
async patchJson<T>(
requestUrl: string,
obj: any,
additionalHeaders: ifm.IHeaders = {}
): Promise<ifm.ITypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2)
additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.TypedResponse<T>> {
const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders,
Headers.Accept,
@@ -310,7 +312,7 @@ export class HttpClient {
Headers.ContentType,
MediaTypes.ApplicationJson
)
let res: ifm.IHttpClientResponse = await this.patch(
const res: HttpClientResponse = await this.patch(
requestUrl,
data,
additionalHeaders
@@ -323,28 +325,28 @@ export class HttpClient {
* All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch
*/
public async request(
async request(
verb: string,
requestUrl: string,
data: string | NodeJS.ReadableStream,
headers: ifm.IHeaders
): Promise<ifm.IHttpClientResponse> {
data: string | NodeJS.ReadableStream | null,
headers?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse> {
if (this._disposed) {
throw new Error('Client has already been disposed.')
}
let parsedUrl = new URL(requestUrl)
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers)
const parsedUrl = new URL(requestUrl)
let info: ifm.RequestInfo = this._prepareRequest(verb, parsedUrl, headers)
// Only perform retries on reads since writes may not be idempotent.
let maxTries: number =
this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1
const maxTries: number =
this._allowRetries && RetryableHttpVerbs.includes(verb)
? this._maxRetries + 1
: 1
let numTries: number = 0
let numTries = 0
let response: HttpClientResponse
while (numTries < maxTries) {
let response: HttpClientResponse | undefined
do {
response = await this.requestRaw(info, data)
// Check if it's an authentication challenge
@@ -353,11 +355,11 @@ export class HttpClient {
response.message &&
response.message.statusCode === HttpCodes.Unauthorized
) {
let authenticationHandler: ifm.IRequestHandler
let authenticationHandler: ifm.RequestHandler | undefined
for (let i = 0; i < this.handlers.length; i++) {
if (this.handlers[i].canHandleAuthentication(response)) {
authenticationHandler = this.handlers[i]
for (const handler of this.handlers) {
if (handler.canHandleAuthentication(response)) {
authenticationHandler = handler
break
}
}
@@ -373,19 +375,21 @@ export class HttpClient {
let redirectsRemaining: number = this._maxRedirects
while (
HttpRedirectCodes.indexOf(response.message.statusCode) != -1 &&
response.message.statusCode &&
HttpRedirectCodes.includes(response.message.statusCode) &&
this._allowRedirects &&
redirectsRemaining > 0
) {
const redirectUrl: string | null = response.message.headers['location']
const redirectUrl: string | undefined =
response.message.headers['location']
if (!redirectUrl) {
// if there's no location to redirect to, we won't
break
}
let parsedRedirectUrl = new URL(redirectUrl)
const parsedRedirectUrl = new URL(redirectUrl)
if (
parsedUrl.protocol == 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol &&
parsedUrl.protocol === 'https:' &&
parsedUrl.protocol !== parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade
) {
throw new Error(
@@ -399,7 +403,7 @@ export class HttpClient {
// strip authorization header if redirected to a different hostname
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
for (let header in headers) {
for (const header in headers) {
// header names are case insensitive
if (header.toLowerCase() === 'authorization') {
delete headers[header]
@@ -413,7 +417,10 @@ export class HttpClient {
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
return response
}
@@ -424,7 +431,7 @@ export class HttpClient {
await response.readBody()
await this._performExponentialBackoff(numTries)
}
}
} while (numTries < maxTries)
return response
}
@@ -432,7 +439,7 @@ export class HttpClient {
/**
* Needs to be called if keepAlive is set to true in request options.
*/
public dispose() {
dispose(): void {
if (this._agent) {
this._agent.destroy()
}
@@ -445,20 +452,20 @@ export class HttpClient {
* @param info
* @param data
*/
public requestRaw(
info: ifm.IRequestInfo,
data: string | NodeJS.ReadableStream
): Promise<ifm.IHttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => {
let callbackForResult = function (
err: any,
res: ifm.IHttpClientResponse
) {
async requestRaw(
info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream | null
): Promise<HttpClientResponse> {
return new Promise<HttpClientResponse>((resolve, reject) => {
function callbackForResult(err?: Error, res?: HttpClientResponse): void {
if (err) {
reject(err)
} else if (!res) {
// If `err` is not passed, then `res` must be passed.
reject(new Error('Unknown error'))
} else {
resolve(res)
}
resolve(res)
}
this.requestRawWithCallback(info, data, callbackForResult)
@@ -471,33 +478,35 @@ export class HttpClient {
* @param data
* @param onResult
*/
public requestRawWithCallback(
info: ifm.IRequestInfo,
data: string | NodeJS.ReadableStream,
onResult: (err: any, res: ifm.IHttpClientResponse) => void
requestRawWithCallback(
info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream | null,
onResult: (err?: Error, res?: HttpClientResponse) => void
): void {
let socket
if (typeof data === 'string') {
if (!info.options.headers) {
info.options.headers = {}
}
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
}
let callbackCalled: boolean = false
let handleResult = (err: any, res: HttpClientResponse) => {
let callbackCalled = false
function handleResult(err?: Error, res?: HttpClientResponse): void {
if (!callbackCalled) {
callbackCalled = true
onResult(err, res)
}
}
let req: http.ClientRequest = info.httpModule.request(
const req: http.ClientRequest = info.httpModule.request(
info.options,
(msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg)
handleResult(null, res)
const res: HttpClientResponse = new HttpClientResponse(msg)
handleResult(undefined, res)
}
)
let socket: net.Socket
req.on('socket', sock => {
socket = sock
})
@@ -507,13 +516,13 @@ export class HttpClient {
if (socket) {
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
// res should have headers
handleResult(err, null)
handleResult(err)
})
if (data && typeof data === 'string') {
@@ -521,7 +530,7 @@ export class HttpClient {
}
if (data && typeof data !== 'string') {
data.on('close', function () {
data.on('close', function() {
req.end()
})
@@ -536,17 +545,17 @@ export class HttpClient {
* routing through a proxy server - depending upon the url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/
public getAgent(serverUrl: string): http.Agent {
let parsedUrl = new URL(serverUrl)
getAgent(serverUrl: string): http.Agent {
const parsedUrl = new URL(serverUrl)
return this._getAgent(parsedUrl)
}
private _prepareRequest(
method: string,
requestUrl: URL,
headers: ifm.IHeaders
): ifm.IRequestInfo {
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{}
headers?: http.OutgoingHttpHeaders
): ifm.RequestInfo {
const info: ifm.RequestInfo = <ifm.RequestInfo>{}
info.parsedUrl = requestUrl
const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
@@ -570,23 +579,22 @@ export class HttpClient {
// gives handlers an opportunity to participate
if (this.handlers) {
this.handlers.forEach(handler => {
for (const handler of this.handlers) {
handler.prepareRequest(info.options)
})
}
}
return info
}
private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders {
const lowercaseKeys = obj =>
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
private _mergeHeaders(
headers?: http.OutgoingHttpHeaders
): http.OutgoingHttpHeaders {
if (this.requestOptions && this.requestOptions.headers) {
return Object.assign(
{},
lowercaseKeys(this.requestOptions.headers),
lowercaseKeys(headers)
lowercaseKeys(headers || {})
)
}
@@ -594,14 +602,11 @@ export class HttpClient {
}
private _getExistingOrDefaultHeader(
additionalHeaders: ifm.IHeaders,
additionalHeaders: http.OutgoingHttpHeaders,
header: string,
_default: string
) {
const lowercaseKeys = obj =>
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
let clientHeader: string
): string | number | string[] {
let clientHeader: string | undefined
if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
}
@@ -610,8 +615,8 @@ export class HttpClient {
private _getAgent(parsedUrl: URL): http.Agent {
let agent
let proxyUrl: URL = pm.getProxyUrl(parsedUrl)
let useProxy = proxyUrl && proxyUrl.hostname
const proxyUrl = pm.getProxyUrl(parsedUrl)
const useProxy = proxyUrl && proxyUrl.hostname
if (this._keepAlive && useProxy) {
agent = this._proxyAgent
@@ -622,24 +627,20 @@ export class HttpClient {
}
// if agent is already assigned use that agent.
if (!!agent) {
if (agent) {
return agent
}
const usingSsl = parsedUrl.protocol === 'https:'
let maxSockets = 100
if (!!this.requestOptions) {
if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
}
if (useProxy) {
// If using proxy, need tunnel
if (!tunnel) {
tunnel = require('tunnel')
}
// This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
if (proxyUrl && proxyUrl.hostname) {
const agentOptions = {
maxSockets: maxSockets,
maxSockets,
keepAlive: this._keepAlive,
proxy: {
...((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 (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)
this._agent = agent
}
@@ -686,50 +687,51 @@ export class HttpClient {
return agent
}
private _performExponentialBackoff(retryNumber: number): Promise<void> {
private async _performExponentialBackoff(retryNumber: number): Promise<void> {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
return new Promise(resolve => setTimeout(() => resolve(), ms))
}
private static dateTimeDeserializer(key: any, value: any): any {
if (typeof value === 'string') {
let a = new Date(value)
if (!isNaN(a.valueOf())) {
return a
}
}
return value
}
private async _processResponse<T>(
res: ifm.IHttpClientResponse,
options: ifm.IRequestOptions
): Promise<ifm.ITypedResponse<T>> {
return new Promise<ifm.ITypedResponse<T>>(async (resolve, reject) => {
const statusCode: number = res.message.statusCode
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.ITypedResponse<T> = {
statusCode: statusCode,
const response: ifm.TypedResponse<T> = {
statusCode,
result: null,
headers: {}
}
// not found leads to null obj returned
if (statusCode == HttpCodes.NotFound) {
if (statusCode === HttpCodes.NotFound) {
resolve(response)
}
let obj: any
let contents: string
// get the result from the body
function dateTimeDeserializer(key: any, value: any): any {
if (typeof value === 'string') {
const a = new Date(value)
if (!isNaN(a.valueOf())) {
return a
}
}
return value
}
let obj: any
let contents: string | undefined
try {
contents = await res.readBody()
if (contents && contents.length > 0) {
if (options && options.deserializeDates) {
obj = JSON.parse(contents, HttpClient.dateTimeDeserializer)
obj = JSON.parse(contents, dateTimeDeserializer)
} else {
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
msg = contents
} 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
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 {
[key: string]: any
}
export interface IHttpClient {
export interface HttpClient {
options(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
get(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
del(
requestUrl: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
post(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
patch(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
put(
requestUrl: string,
data: string,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
sendStream(
verb: string,
requestUrl: string,
stream: NodeJS.ReadableStream,
additionalHeaders?: IHeaders
): Promise<IHttpClientResponse>
additionalHeaders?: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
request(
verb: string,
requestUrl: string,
data: string | NodeJS.ReadableStream,
headers: IHeaders
): Promise<IHttpClientResponse>
headers: http.OutgoingHttpHeaders
): Promise<HttpClientResponse>
requestRaw(
info: IRequestInfo,
info: RequestInfo,
data: string | NodeJS.ReadableStream
): Promise<IHttpClientResponse>
): Promise<HttpClientResponse>
requestRawWithCallback(
info: IRequestInfo,
info: RequestInfo,
data: string | NodeJS.ReadableStream,
onResult: (err: any, res: IHttpClientResponse) => void
onResult: (err?: Error, res?: HttpClientResponse) => void
): void
}
export interface IRequestHandler {
export interface RequestHandler {
prepareRequest(options: http.RequestOptions): void
canHandleAuthentication(response: IHttpClientResponse): boolean
canHandleAuthentication(response: HttpClientResponse): boolean
handleAuthentication(
httpClient: IHttpClient,
requestInfo: IRequestInfo,
objs
): Promise<IHttpClientResponse>
httpClient: HttpClient,
requestInfo: RequestInfo,
data: string | NodeJS.ReadableStream | null
): Promise<HttpClientResponse>
}
export interface IHttpClientResponse {
message: http.IncomingMessage
readBody(): Promise<string>
}
export interface IRequestInfo {
export interface RequestInfo {
options: http.RequestOptions
parsedUrl: URL
httpModule: any
httpModule: typeof http | typeof https
}
export interface IRequestOptions {
headers?: IHeaders
export interface RequestOptions {
headers?: http.OutgoingHttpHeaders
socketTimeout?: number
ignoreSslError?: boolean
allowRedirects?: boolean
@@ -91,8 +84,8 @@ export interface IRequestOptions {
maxRetries?: number
}
export interface ITypedResponse<T> {
export interface TypedResponse<T> {
statusCode: number
result: T | null
headers: Object
headers: http.IncomingHttpHeaders
}
+16 -16
View File
@@ -1,23 +1,23 @@
export function getProxyUrl(reqUrl: URL): URL | undefined {
let usingSsl = reqUrl.protocol === 'https:'
const usingSsl = reqUrl.protocol === 'https:'
let proxyUrl: URL
if (checkBypass(reqUrl)) {
return proxyUrl
return undefined
}
let proxyVar: string
if (usingSsl) {
proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY']
} else {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY']
}
const proxyVar = (() => {
if (usingSsl) {
return process.env['https_proxy'] || process.env['HTTPS_PROXY']
} else {
return process.env['http_proxy'] || process.env['HTTP_PROXY']
}
})()
if (proxyVar) {
proxyUrl = new URL(proxyVar)
return new URL(proxyVar)
} else {
return undefined
}
return proxyUrl
}
export function checkBypass(reqUrl: URL): boolean {
@@ -25,13 +25,13 @@ export function checkBypass(reqUrl: URL): boolean {
return false
}
let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
if (!noProxy) {
return false
}
// Determine the request port
let reqPort: number
let reqPort: number | undefined
if (reqUrl.port) {
reqPort = Number(reqUrl.port)
} else if (reqUrl.protocol === 'http:') {
@@ -41,13 +41,13 @@ export function checkBypass(reqUrl: URL): boolean {
}
// Format the request hostname and hostname with port
let upperReqHosts = [reqUrl.hostname.toUpperCase()]
const upperReqHosts = [reqUrl.hostname.toUpperCase()]
if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
}
// Compare request host against noproxy
for (let upperNoProxyItem of noProxy
for (const upperNoProxyItem of noProxy
.split(',')
.map(x => x.trim().toUpperCase())
.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
### 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
- 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",
"version": "1.7.2",
"version": "2.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/tool-cache",
"version": "1.7.2",
"version": "2.0.0",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1",
"semver": "^6.1.0",
"uuid": "^3.3.2"
@@ -24,30 +24,41 @@
}
},
"node_modules/@actions/core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"dependencies": {
"@actions/io": "^1.0.1"
}
},
"node_modules/@actions/http-client": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
"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"
"tunnel": "^0.0.6"
}
},
"node_modules/@actions/io": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
},
"node_modules/@types/nock": {
"version": "10.0.3",
@@ -280,30 +291,43 @@
},
"dependencies": {
"@actions/core": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.8.0.tgz",
"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": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
"requires": {
"@actions/io": "^1.0.1"
}
},
"@actions/http-client": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
"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"
"tunnel": "^0.0.6"
}
},
"@actions/io": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
},
"@types/nock": {
"version": "10.0.3",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/tool-cache",
"version": "1.7.2",
"version": "2.0.1",
"description": "Actions tool-cache lib",
"keywords": [
"github",
@@ -38,7 +38,7 @@
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8",
"@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1",
"semver": "^6.1.0",
"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 stream from 'stream'
import * as util from 'util'
import {ok} from 'assert'
import {OutgoingHttpHeaders} from 'http'
import uuidV4 from 'uuid/v4'
import {exec} from '@actions/exec/lib/exec'
import {ExecOptions} from '@actions/exec/lib/interfaces'
import {ok} from 'assert'
import {RetryHelper} from './retry-helper'
import {IHeaders} from '@actions/http-client/interfaces'
export class HTTPError extends Error {
constructor(readonly httpStatusCode: number | undefined) {
@@ -39,7 +39,7 @@ export async function downloadTool(
url: string,
dest?: string,
auth?: string,
headers?: IHeaders
headers?: OutgoingHttpHeaders
): Promise<string> {
dest = dest || path.join(_getTempDirectory(), uuidV4())
await io.mkdirP(path.dirname(dest))
@@ -82,7 +82,7 @@ async function downloadToolAttempt(
url: string,
dest: string,
auth?: string,
headers?: IHeaders
headers?: OutgoingHttpHeaders
): Promise<string> {
if (fs.existsSync(dest)) {
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 http: httpm.HttpClient = new httpm.HttpClient('tool-cache')
const headers: IHeaders = {}
const headers: OutgoingHttpHeaders = {}
if (auth) {
core.debug('set auth')
headers.authorization = auth
+2 -2
View File
@@ -9,5 +9,5 @@ if [[ -z "$name" ]]; then
exit 1
fi
lerna create @actions/$name
cp packages/toolkit/tsconfig.json packages/$name/tsconfig.json
npx lerna create @actions/$name
cp packages/core/tsconfig.json packages/$name/tsconfig.json