Compare commits

..
Author SHA1 Message Date
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
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
21 changed files with 836 additions and 10958 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ on:
inputs: inputs:
package: package:
required: true required: true
description: 'core, artifact, cache, exec, github, glob, io, tool-cache' description: 'core, artifact, cache, exec, github, glob, http-client, io, tool-cache'
jobs: jobs:
test: test:
+9
View File
@@ -46,6 +46,15 @@ $ npm install @actions/glob
``` ```
<br/> <br/>
:phone: [@actions/http-client](packages/http-client)
A lightweight HTTP client optimized for building actions. Read more [here](packages/http-client)
```bash
$ npm install @actions/http-client
```
<br/>
:pencil2: [@actions/io](packages/io) :pencil2: [@actions/io](packages/io)
Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io) Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io)
@@ -1,9 +1,10 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import path from 'path' import path from 'path'
import {markdownSummary, SUMMARY_ENV_VAR} from '../src/markdown-summary' import {summary, SUMMARY_ENV_VAR} from '../src/summary'
const testFilePath = path.join(__dirname, 'test', 'test-summary.md') const testDirectoryPath = path.join(__dirname, 'test')
const testFilePath = path.join(testDirectoryPath, 'test-summary.md')
async function assertSummary(expected: string): Promise<void> { async function assertSummary(expected: string): Promise<void> {
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'}) const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
@@ -67,11 +68,12 @@ const fixtures = {
} }
} }
describe('@actions/core/src/markdown-summary', () => { describe('@actions/core/src/summary', () => {
beforeEach(async () => { beforeEach(async () => {
process.env[SUMMARY_ENV_VAR] = testFilePath process.env[SUMMARY_ENV_VAR] = testFilePath
await fs.promises.mkdir(testDirectoryPath, {recursive: true})
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
markdownSummary.emptyBuffer() summary.emptyBuffer()
}) })
afterAll(async () => { afterAll(async () => {
@@ -80,39 +82,39 @@ describe('@actions/core/src/markdown-summary', () => {
it('throws if summary env var is undefined', async () => { it('throws if summary env var is undefined', async () => {
process.env[SUMMARY_ENV_VAR] = undefined process.env[SUMMARY_ENV_VAR] = undefined
const write = markdownSummary.addRaw(fixtures.text).write() const write = summary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow() await expect(write).rejects.toThrow()
}) })
it('throws if summary file does not exist', async () => { it('throws if summary file does not exist', async () => {
await fs.promises.unlink(testFilePath) await fs.promises.unlink(testFilePath)
const write = markdownSummary.addRaw(fixtures.text).write() const write = summary.addRaw(fixtures.text).write()
await expect(write).rejects.toThrow() await expect(write).rejects.toThrow()
}) })
it('appends text to summary file', async () => { it('appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write() await summary.addRaw(fixtures.text).write()
await assertSummary(`# ${fixtures.text}`) await assertSummary(`# ${fixtures.text}`)
}) })
it('overwrites text to summary file', async () => { it('overwrites text to summary file', async () => {
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write({overwrite: true}) await summary.addRaw(fixtures.text).write({overwrite: true})
await assertSummary(fixtures.text) await assertSummary(fixtures.text)
}) })
it('appends text with EOL to summary file', async () => { it('appends text with EOL to summary file', async () => {
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text, true).write() await summary.addRaw(fixtures.text, true).write()
await assertSummary(`# ${fixtures.text}${os.EOL}`) await assertSummary(`# ${fixtures.text}${os.EOL}`)
}) })
it('chains appends text to summary file', async () => { it('chains appends text to summary file', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary await summary
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addRaw(fixtures.text) .addRaw(fixtures.text)
@@ -122,33 +124,33 @@ describe('@actions/core/src/markdown-summary', () => {
it('empties buffer after write', async () => { it('empties buffer after write', async () => {
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
await markdownSummary.addRaw(fixtures.text).write() await summary.addRaw(fixtures.text).write()
await assertSummary(fixtures.text) await assertSummary(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('returns summary buffer as string', () => { it('returns summary buffer as string', () => {
markdownSummary.addRaw(fixtures.text) summary.addRaw(fixtures.text)
expect(markdownSummary.stringify()).toEqual(fixtures.text) expect(summary.stringify()).toEqual(fixtures.text)
}) })
it('return correct values for isEmptyBuffer', () => { it('return correct values for isEmptyBuffer', () => {
markdownSummary.addRaw(fixtures.text) summary.addRaw(fixtures.text)
expect(markdownSummary.isEmptyBuffer()).toBe(false) expect(summary.isEmptyBuffer()).toBe(false)
markdownSummary.emptyBuffer() summary.emptyBuffer()
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('clears a buffer and summary file', async () => { it('clears a buffer and summary file', async () => {
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'}) await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
await markdownSummary.clear() await summary.clear()
await assertSummary('') await assertSummary('')
expect(markdownSummary.isEmptyBuffer()).toBe(true) expect(summary.isEmptyBuffer()).toBe(true)
}) })
it('adds EOL', async () => { it('adds EOL', async () => {
await markdownSummary await summary
.addRaw(fixtures.text) .addRaw(fixtures.text)
.addEOL() .addEOL()
.write() .write()
@@ -156,37 +158,37 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds a code block without language', async () => { it('adds a code block without language', async () => {
await markdownSummary.addCodeBlock(fixtures.code).write() await summary.addCodeBlock(fixtures.code).write()
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}` const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a code block with a language', async () => { it('adds a code block with a language', async () => {
await markdownSummary.addCodeBlock(fixtures.code, 'go').write() await summary.addCodeBlock(fixtures.code, 'go').write()
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}` const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an unordered list', async () => { it('adds an unordered list', async () => {
await markdownSummary.addList(fixtures.list).write() await summary.addList(fixtures.list).write()
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}` const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an ordered list', async () => { it('adds an ordered list', async () => {
await markdownSummary.addList(fixtures.list, true).write() await summary.addList(fixtures.list, true).write()
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}` const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a table', async () => { it('adds a table', async () => {
await markdownSummary.addTable(fixtures.table).write() await summary.addTable(fixtures.table).write()
const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}` const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a details element', async () => { it('adds a details element', async () => {
await markdownSummary await summary
.addDetails(fixtures.details.label, fixtures.details.content) .addDetails(fixtures.details.label, fixtures.details.content)
.write() .write()
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}` const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
@@ -194,13 +196,13 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds an image with alt text', async () => { it('adds an image with alt text', async () => {
await markdownSummary.addImage(fixtures.img.src, fixtures.img.alt).write() await summary.addImage(fixtures.img.src, fixtures.img.alt).write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds an image with custom dimensions', async () => { it('adds an image with custom dimensions', async () => {
await markdownSummary await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options) .addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write() .write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -208,7 +210,7 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds an image with custom dimensions', async () => { it('adds an image with custom dimensions', async () => {
await markdownSummary await summary
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options) .addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
.write() .write()
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}` const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
@@ -217,21 +219,21 @@ describe('@actions/core/src/markdown-summary', () => {
it('adds headings h1...h6', async () => { it('adds headings h1...h6', async () => {
for (const i of [1, 2, 3, 4, 5, 6]) { for (const i of [1, 2, 3, 4, 5, 6]) {
markdownSummary.addHeading('heading', i) summary.addHeading('heading', i)
} }
await markdownSummary.write() await summary.write()
const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}` const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds h1 if heading level not specified', async () => { it('adds h1 if heading level not specified', async () => {
await markdownSummary.addHeading('heading').write() await summary.addHeading('heading').write()
const expected = `<h1>heading</h1>${os.EOL}` const expected = `<h1>heading</h1>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('uses h1 if heading level is garbage or out of range', async () => { it('uses h1 if heading level is garbage or out of range', async () => {
await markdownSummary await summary
.addHeading('heading', 'foobar') .addHeading('heading', 'foobar')
.addHeading('heading', 1337) .addHeading('heading', 1337)
.addHeading('heading', -1) .addHeading('heading', -1)
@@ -242,35 +244,31 @@ describe('@actions/core/src/markdown-summary', () => {
}) })
it('adds a separator', async () => { it('adds a separator', async () => {
await markdownSummary.addSeparator().write() await summary.addSeparator().write()
const expected = `<hr>${os.EOL}` const expected = `<hr>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a break', async () => { it('adds a break', async () => {
await markdownSummary.addBreak().write() await summary.addBreak().write()
const expected = `<br>${os.EOL}` const expected = `<br>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a quote', async () => { it('adds a quote', async () => {
await markdownSummary.addQuote(fixtures.quote.text).write() await summary.addQuote(fixtures.quote.text).write()
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}` const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a quote with citation', async () => { it('adds a quote with citation', async () => {
await markdownSummary await summary.addQuote(fixtures.quote.text, fixtures.quote.cite).write()
.addQuote(fixtures.quote.text, fixtures.quote.cite)
.write()
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}` const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
it('adds a link with href', async () => { it('adds a link with href', async () => {
await markdownSummary await summary.addLink(fixtures.link.text, fixtures.link.href).write()
.addLink(fixtures.link.text, fixtures.link.href)
.write()
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}` const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
await assertSummary(expected) await assertSummary(expected)
}) })
+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'
@@ -4,7 +4,7 @@ const {access, appendFile, writeFile} = promises
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY' export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
export const SUMMARY_DOCS_URL = export const SUMMARY_DOCS_URL =
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary' 'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'
export type SummaryTableRow = (SummaryTableCell | string)[] export type SummaryTableRow = (SummaryTableCell | string)[]
@@ -51,7 +51,7 @@ export interface SummaryWriteOptions {
overwrite?: boolean overwrite?: boolean
} }
class MarkdownSummary { class Summary {
private _buffer: string private _buffer: string
private _filePath?: string private _filePath?: string
@@ -73,7 +73,7 @@ class MarkdownSummary {
const pathFromEnv = process.env[SUMMARY_ENV_VAR] const pathFromEnv = process.env[SUMMARY_ENV_VAR]
if (!pathFromEnv) { if (!pathFromEnv) {
throw new Error( throw new Error(
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.` `Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`
) )
} }
@@ -119,9 +119,9 @@ class MarkdownSummary {
* *
* @param {SummaryWriteOptions} [options] (optional) options for write operation * @param {SummaryWriteOptions} [options] (optional) options for write operation
* *
* @returns {Promise<MarkdownSummary>} markdown summary instance * @returns {Promise<Summary>} summary instance
*/ */
async write(options?: SummaryWriteOptions): Promise<MarkdownSummary> { async write(options?: SummaryWriteOptions): Promise<Summary> {
const overwrite = !!options?.overwrite const overwrite = !!options?.overwrite
const filePath = await this.filePath() const filePath = await this.filePath()
const writeFunc = overwrite ? writeFile : appendFile const writeFunc = overwrite ? writeFile : appendFile
@@ -132,9 +132,9 @@ class MarkdownSummary {
/** /**
* Clears the summary buffer and wipes the summary file * Clears the summary buffer and wipes the summary file
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
async clear(): Promise<MarkdownSummary> { async clear(): Promise<Summary> {
return this.emptyBuffer().write({overwrite: true}) return this.emptyBuffer().write({overwrite: true})
} }
@@ -159,9 +159,9 @@ class MarkdownSummary {
/** /**
* Resets the summary buffer without writing to summary file * Resets the summary buffer without writing to summary file
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
emptyBuffer(): MarkdownSummary { emptyBuffer(): Summary {
this._buffer = '' this._buffer = ''
return this return this
} }
@@ -172,9 +172,9 @@ class MarkdownSummary {
* @param {string} text content to add * @param {string} text content to add
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false) * @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addRaw(text: string, addEOL = false): MarkdownSummary { addRaw(text: string, addEOL = false): Summary {
this._buffer += text this._buffer += text
return addEOL ? this.addEOL() : this return addEOL ? this.addEOL() : this
} }
@@ -182,9 +182,9 @@ class MarkdownSummary {
/** /**
* Adds the operating system-specific end-of-line marker to the buffer * Adds the operating system-specific end-of-line marker to the buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addEOL(): MarkdownSummary { addEOL(): Summary {
return this.addRaw(EOL) return this.addRaw(EOL)
} }
@@ -194,9 +194,9 @@ class MarkdownSummary {
* @param {string} code content to render within fenced code block * @param {string} code content to render within fenced code block
* @param {string} lang (optional) language to syntax highlight code * @param {string} lang (optional) language to syntax highlight code
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addCodeBlock(code: string, lang?: string): MarkdownSummary { addCodeBlock(code: string, lang?: string): Summary {
const attrs = { const attrs = {
...(lang && {lang}) ...(lang && {lang})
} }
@@ -210,9 +210,9 @@ class MarkdownSummary {
* @param {string[]} items list of items to render * @param {string[]} items list of items to render
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false) * @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addList(items: string[], ordered = false): MarkdownSummary { addList(items: string[], ordered = false): Summary {
const tag = ordered ? 'ol' : 'ul' const tag = ordered ? 'ol' : 'ul'
const listItems = items.map(item => this.wrap('li', item)).join('') const listItems = items.map(item => this.wrap('li', item)).join('')
const element = this.wrap(tag, listItems) const element = this.wrap(tag, listItems)
@@ -224,9 +224,9 @@ class MarkdownSummary {
* *
* @param {SummaryTableCell[]} rows table rows * @param {SummaryTableCell[]} rows table rows
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addTable(rows: SummaryTableRow[]): MarkdownSummary { addTable(rows: SummaryTableRow[]): Summary {
const tableBody = rows const tableBody = rows
.map(row => { .map(row => {
const cells = row const cells = row
@@ -260,9 +260,9 @@ class MarkdownSummary {
* @param {string} label text for the closed state * @param {string} label text for the closed state
* @param {string} content collapsable content * @param {string} content collapsable content
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addDetails(label: string, content: string): MarkdownSummary { addDetails(label: string, content: string): Summary {
const element = this.wrap('details', this.wrap('summary', label) + content) const element = this.wrap('details', this.wrap('summary', label) + content)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -274,13 +274,9 @@ class MarkdownSummary {
* @param {string} alt text description of the image * @param {string} alt text description of the image
* @param {SummaryImageOptions} options (optional) addition image attributes * @param {SummaryImageOptions} options (optional) addition image attributes
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addImage( addImage(src: string, alt: string, options?: SummaryImageOptions): Summary {
src: string,
alt: string,
options?: SummaryImageOptions
): MarkdownSummary {
const {width, height} = options || {} const {width, height} = options || {}
const attrs = { const attrs = {
...(width && {width}), ...(width && {width}),
@@ -297,9 +293,9 @@ class MarkdownSummary {
* @param {string} text heading text * @param {string} text heading text
* @param {number | string} [level=1] (optional) the heading level, default: 1 * @param {number | string} [level=1] (optional) the heading level, default: 1
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addHeading(text: string, level?: number | string): MarkdownSummary { addHeading(text: string, level?: number | string): Summary {
const tag = `h${level}` const tag = `h${level}`
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag) const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
? tag ? tag
@@ -311,9 +307,9 @@ class MarkdownSummary {
/** /**
* Adds an HTML thematic break (<hr>) to the summary buffer * Adds an HTML thematic break (<hr>) to the summary buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addSeparator(): MarkdownSummary { addSeparator(): Summary {
const element = this.wrap('hr', null) const element = this.wrap('hr', null)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -321,9 +317,9 @@ class MarkdownSummary {
/** /**
* Adds an HTML line break (<br>) to the summary buffer * Adds an HTML line break (<br>) to the summary buffer
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addBreak(): MarkdownSummary { addBreak(): Summary {
const element = this.wrap('br', null) const element = this.wrap('br', null)
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
@@ -334,9 +330,9 @@ class MarkdownSummary {
* @param {string} text quote text * @param {string} text quote text
* @param {string} cite (optional) citation url * @param {string} cite (optional) citation url
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addQuote(text: string, cite?: string): MarkdownSummary { addQuote(text: string, cite?: string): Summary {
const attrs = { const attrs = {
...(cite && {cite}) ...(cite && {cite})
} }
@@ -350,13 +346,18 @@ class MarkdownSummary {
* @param {string} text link text/content * @param {string} text link text/content
* @param {string} href hyperlink * @param {string} href hyperlink
* *
* @returns {MarkdownSummary} markdown summary instance * @returns {Summary} summary instance
*/ */
addLink(text: string, href: string): MarkdownSummary { addLink(text: string, href: string): Summary {
const element = this.wrap('a', text, {href}) const element = this.wrap('a', text, {href})
return this.addRaw(element).addEOL() return this.addRaw(element).addEOL()
} }
} }
// singleton export const _summary = new Summary()
export const markdownSummary = new MarkdownSummary()
/**
* @deprecated use `core.summary`
*/
export const markdownSummary = _summary
export const summary = _summary
-3
View File
@@ -1,5 +1,2 @@
_out
node_modules
.DS_Store
testoutput.txt testoutput.txt
npm-debug.log npm-debug.log
+13 -19
View File
@@ -1,18 +1,11 @@
# `@actions/http-client`
<p align="center"> A lightweight HTTP client optimized for building actions.
<img src="actions.png">
</p>
# Actions Http-Client
[![Http Status](https://github.com/actions/http-client/workflows/http-tests/badge.svg)](https://github.com/actions/http-client/actions)
A lightweight HTTP client optimized for use with actions, TypeScript with generics and async await.
## Features ## Features
- HTTP client with TypeScript generics and async/await/Promises - HTTP client with TypeScript generics and async/await/Promises
- Typings included so no need to acquire separately (great for intellisense and no versioning drift) - Typings included!
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner - [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner
- Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+. - Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+.
- Basic, Bearer and PAT Support out of the box. Extensible handlers for others. - Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
@@ -28,7 +21,7 @@ npm install @actions/http-client --save
## Samples ## Samples
See the [HTTP](./__tests__) tests for detailed examples. See the [tests](./__tests__) for detailed examples.
## Errors ## Errors
@@ -39,13 +32,13 @@ The HTTP client does not throw unless truly exceptional.
* A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body. * A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body.
* Redirects (3xx) will be followed by default. * Redirects (3xx) will be followed by default.
See [HTTP tests](./__tests__) for detailed examples. See the [tests](./__tests__) for detailed examples.
## Debugging ## Debugging
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible: To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
``` ```shell
export NODE_DEBUG=http export NODE_DEBUG=http
``` ```
@@ -63,17 +56,18 @@ We welcome PRs. Please create an issue and if applicable, a design before proce
once: once:
```bash ```
$ npm install npm install
``` ```
To build: To build:
```bash ```
$ npm run build npm run build
``` ```
To run all tests: To run all tests:
```bash
$ npm test ```
npm test
``` ```
+10
View File
@@ -1,5 +1,15 @@
## Releases ## Releases
## 2.0.0
- The package is now compiled with TypeScript's [`strict` compiler setting](https://www.typescriptlang.org/tsconfig#strict). To comply with stricter rules:
- Some exported types now include `| null` or `| undefined`, matching their actual behavior.
- Types implementing the method `RequestHandler.handleAuthentication()` now throw an `Error` rather than returning `null` if they do not support handling an HTTP 401 response. Callers can still use `canHandleAuthentication()` to determine if this handling is supported or not.
- Types using `any` have been scoped to more specific types.
- Following TypeScript's naming conventions, exported interfaces no longer begin with the prefix `I-`.
- Delete the `IHttpClientResponse` interface in favor of the `HttpClientResponse` class.
- Delete the `IHeaders` interface in favor of `http.OutgoingHttpHeaders`.
- The source code of the package was moved to build with [actions/toolkit](https://github.com/actions/toolkit).
## 1.0.11 ## 1.0.11
Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42) Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
+38 -26
View File
@@ -1,5 +1,5 @@
import * as httpm from '../_out' import * as httpm from '../lib'
import * as am from '../_out/auth' import * as am from '../lib/auth'
describe('auth', () => { describe('auth', () => {
beforeEach(() => {}) beforeEach(() => {})
@@ -7,17 +7,21 @@ describe('auth', () => {
afterEach(() => {}) afterEach(() => {})
it('does basic http get request with basic auth', async () => { it('does basic http get request with basic auth', async () => {
let bh: am.BasicCredentialHandler = new am.BasicCredentialHandler( const bh: am.BasicCredentialHandler = new am.BasicCredentialHandler(
'johndoe', 'johndoe',
'password' 'password'
) )
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [bh]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') bh
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
let creds: string = Buffer.from( const creds: string = Buffer.from(
auth.substring('Basic '.length), auth.substring('Basic '.length),
'base64' 'base64'
).toString() ).toString()
@@ -26,36 +30,44 @@ describe('auth', () => {
}) })
it('does basic http get request with pat token auth', async () => { it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler( const ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
token token
) )
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
let creds: string = Buffer.from( const creds: string = Buffer.from(
auth.substring('Basic '.length), auth.substring('Basic '.length),
'base64' 'base64'
).toString() ).toString()
expect(creds).toBe('PAT:' + token) expect(creds).toBe(`PAT:${token}`)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
}) })
it('does basic http get request with pat token auth', async () => { it('does basic http get request with pat token auth', async () => {
let token: string = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs' const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
let ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token) const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [ph]) const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') ph
])
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
let auth: string = obj.headers.Authorization const auth: string = obj.headers.Authorization
expect(auth).toBe('Bearer ' + token) expect(auth).toBe(`Bearer ${token}`)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
}) })
}) })
+146 -147
View File
@@ -1,9 +1,10 @@
import * as httpm from '../_out' /* eslint-disable @typescript-eslint/no-explicit-any */
import * as ifm from '../_out/interfaces'
import * as httpm from '..'
import * as path from 'path' import * as path from 'path'
import * as fs from 'fs' import * as fs from 'fs'
let sampleFilePath: string = path.join(__dirname, 'testoutput.txt') const sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
interface HttpBinData { interface HttpBinData {
url: string url: string
@@ -23,7 +24,7 @@ describe('basics', () => {
afterEach(() => {}) afterEach(() => {})
it('constructs', () => { it('constructs', () => {
let http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests') const http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests')
expect(http).toBeDefined() expect(http).toBeDefined()
}) })
@@ -39,264 +40,259 @@ describe('basics', () => {
// "url": "https://httpbin.org/get" // "url": "https://httpbin.org/get"
// } // }
it('does basic http get request', async done => { it('does basic http get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeTruthy() expect(obj.headers['User-Agent']).toBeTruthy()
done()
}) })
it('does basic http get request with no user agent', async done => { it('does basic http get request with no user agent', async () => {
let http: httpm.HttpClient = new httpm.HttpClient() const http: httpm.HttpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(obj.headers['User-Agent']).toBeFalsy() expect(obj.headers['User-Agent']).toBeFalsy()
done()
}) })
it('does basic https get request', async done => { it('does basic https get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('does basic http get request with default headers', async done => { it('does basic http get request with default headers', async () => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) }
let res: httpm.HttpClientResponse = await http.get('http://httpbin.org/get') )
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get'
)
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json') expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe('application/json') expect(obj.headers['Content-Type']).toBe('application/json')
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('does basic http get request with merged headers', async done => { it('does basic http get request with merged headers', async () => {
let http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [], { const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests',
[],
{
headers: { headers: {
Accept: 'application/json', Accept: 'application/json',
'Content-Type': 'application/json' 'Content-Type': 'application/json'
} }
}) }
let res: httpm.HttpClientResponse = await http.get( )
const res: httpm.HttpClientResponse = await http.get(
'http://httpbin.org/get', 'http://httpbin.org/get',
{ {
'content-type': 'application/x-www-form-urlencoded' 'content-type': 'application/x-www-form-urlencoded'
} }
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.headers.Accept).toBe('application/json') expect(obj.headers.Accept).toBe('application/json')
expect(obj.headers['Content-Type']).toBe( expect(obj.headers['Content-Type']).toBe(
'application/x-www-form-urlencoded' 'application/x-www-form-urlencoded'
) )
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('pipes a get request', () => { it('pipes a get request', async () => {
return new Promise<string>(async (resolve, reject) => { return new Promise<void>(async resolve => {
let file: NodeJS.WritableStream = fs.createWriteStream(sampleFilePath) const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://httpbin.org/get')).message ;(await _http.get('https://httpbin.org/get')).message
.pipe(file) .pipe(file)
.on('close', () => { .on('close', () => {
let body: string = fs.readFileSync(sampleFilePath).toString() const body: string = fs.readFileSync(sampleFilePath).toString()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
resolve() resolve()
}) })
}) })
}) })
it('does basic get request with redirects', async done => { it('does basic get request with redirects', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') 'https://httpbin.org/get'
)}`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('does basic get request with redirects (303)', async done => { it('does basic get request with redirects (303)', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') + 'https://httpbin.org/get'
'&status_code=303' )}&status_code=303`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
done()
}) })
it('returns 404 for not found get request on redirect', async done => { it('returns 404 for not found get request on redirect', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/status/404') + 'https://httpbin.org/status/404'
'&status_code=303' )}&status_code=303`
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('does not follow redirects if disabled', async done => { it('does not follow redirects if disabled', async () => {
let http: httpm.HttpClient = new httpm.HttpClient( const http: httpm.HttpClient = new httpm.HttpClient(
'typed-test-client-tests', 'typed-test-client-tests',
null, undefined,
{allowRedirects: false} {allowRedirects: false}
) )
let res: httpm.HttpClientResponse = await http.get( const res: httpm.HttpClientResponse = await http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://httpbin.org/get') 'https://httpbin.org/get'
)}`
) )
expect(res.message.statusCode).toBe(302) expect(res.message.statusCode).toBe(302)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('does not pass auth with diff hostname redirects', async done => { it('does not pass auth with diff hostname redirects', async () => {
let headers = { const headers = {
accept: 'application/json', accept: 'application/json',
authorization: 'shhh' authorization: 'shhh'
} }
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://www.httpbin.org/get'), 'https://www.httpbin.org/get'
)}`,
headers headers
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
// httpbin "fixes" the casing // httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json') expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get') expect(obj.url).toBe('https://www.httpbin.org/get')
done()
}) })
it('does not pass Auth with diff hostname redirects', async done => { it('does not pass Auth with diff hostname redirects', async () => {
let headers = { const headers = {
Accept: 'application/json', Accept: 'application/json',
Authorization: 'shhh' Authorization: 'shhh'
} }
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://httpbin.org/redirect-to?url=' + `https://httpbin.org/redirect-to?url=${encodeURIComponent(
encodeURIComponent('https://www.httpbin.org/get'), 'https://www.httpbin.org/get'
)}`,
headers headers
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
// httpbin "fixes" the casing // httpbin "fixes" the casing
expect(obj.headers['Accept']).toBe('application/json') expect(obj.headers['Accept']).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.httpbin.org/get') expect(obj.url).toBe('https://www.httpbin.org/get')
done()
}) })
it('does basic head request', async done => { it('does basic head request', async () => {
let res: httpm.HttpClientResponse = await _http.head( const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
done()
}) })
it('does basic http delete request', async done => { it('does basic http delete request', async () => {
let res: httpm.HttpClientResponse = await _http.del( const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete' 'http://httpbin.org/delete'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) JSON.parse(body)
done()
}) })
it('does basic http post request', async done => { it('does basic http post request', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post( const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post', 'http://httpbin.org/post',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post') expect(obj.url).toBe('http://httpbin.org/post')
done()
}) })
it('does basic http patch request', async done => { it('does basic http patch request', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch( const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch', 'http://httpbin.org/patch',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch') expect(obj.url).toBe('http://httpbin.org/patch')
done()
}) })
it('does basic http options request', async done => { it('does basic http options request', async () => {
let res: httpm.HttpClientResponse = await _http.options( const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org' 'http://httpbin.org'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('returns 404 for not found get request', async done => { it('returns 404 for not found get request', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/status/404' 'http://httpbin.org/status/404'
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
let body: string = await res.readBody() await res.readBody()
done()
}) })
it('gets a json object', async () => { it('gets a json object', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson< const jsonObj = await _http.getJson<HttpBinData>('https://httpbin.org/get')
HttpBinData
>('https://httpbin.org/get')
expect(jsonObj.statusCode).toBe(200) expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined() expect(jsonObj.result).toBeDefined()
expect(jsonObj.result.url).toBe('https://httpbin.org/get') expect(jsonObj.result?.url).toBe('https://httpbin.org/get')
expect(jsonObj.result.headers['Accept']).toBe( expect(jsonObj.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe( expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
@@ -305,26 +301,27 @@ describe('basics', () => {
}) })
it('getting a non existent json object returns null', async () => { it('getting a non existent json object returns null', async () => {
let jsonObj: ifm.ITypedResponse<HttpBinData> = await _http.getJson< const jsonObj = await _http.getJson<HttpBinData>(
HttpBinData 'https://httpbin.org/status/404'
>('https://httpbin.org/status/404') )
expect(jsonObj.statusCode).toBe(404) expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull() expect(jsonObj.result).toBeNull()
}) })
it('posts a json object', async () => { it('posts a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.postJson< const restRes = await _http.postJson<HttpBinData>(
HttpBinData 'https://httpbin.org/post',
>('https://httpbin.org/post', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/post') expect(restRes.result?.url).toBe('https://httpbin.org/post')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -333,19 +330,20 @@ describe('basics', () => {
}) })
it('puts a json object', async () => { it('puts a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.putJson< const restRes = await _http.putJson<HttpBinData>(
HttpBinData 'https://httpbin.org/put',
>('https://httpbin.org/put', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/put') expect(restRes.result?.url).toBe('https://httpbin.org/put')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
@@ -354,18 +352,19 @@ describe('basics', () => {
}) })
it('patch a json object', async () => { it('patch a json object', async () => {
let res: any = {name: 'foo'} const res = {name: 'foo'}
let restRes: ifm.ITypedResponse<HttpBinData> = await _http.patchJson< const restRes = await _http.patchJson<HttpBinData>(
HttpBinData 'https://httpbin.org/patch',
>('https://httpbin.org/patch', res) res
)
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result.url).toBe('https://httpbin.org/patch') expect(restRes.result?.url).toBe('https://httpbin.org/patch')
expect(restRes.result.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result.headers['Accept']).toBe( expect(restRes.result?.headers['Accept']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.result.headers['Content-Type']).toBe( expect(restRes.result?.headers['Content-Type']).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
expect(restRes.headers[httpm.Headers.ContentType]).toBe( expect(restRes.headers[httpm.Headers.ContentType]).toBe(
+15 -14
View File
@@ -1,5 +1,6 @@
import * as httpm from '../_out' /* eslint-disable @typescript-eslint/no-explicit-any */
import * as ifm from '../_out/interfaces'
import * as httpm from '..'
describe('headers', () => { describe('headers', () => {
let _http: httpm.HttpClient let _http: httpm.HttpClient
@@ -9,8 +10,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on getJson', async () => { it('preserves existing headers on getJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.getJson<any>( let jsonObj = await _http.getJson<any>(
'https://httpbin.org/get', 'https://httpbin.org/get',
additionalHeaders additionalHeaders
) )
@@ -19,7 +20,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -33,8 +34,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on postJson', async () => { it('preserves existing headers on postJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.postJson<any>( let jsonObj = await _http.postJson<any>(
'https://httpbin.org/post', 'https://httpbin.org/post',
{}, {},
additionalHeaders additionalHeaders
@@ -44,7 +45,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -61,8 +62,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on putJson', async () => { it('preserves existing headers on putJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.putJson<any>( let jsonObj = await _http.putJson<any>(
'https://httpbin.org/put', 'https://httpbin.org/put',
{}, {},
additionalHeaders additionalHeaders
@@ -72,7 +73,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -86,8 +87,8 @@ describe('headers', () => {
}) })
it('preserves existing headers on patchJson', async () => { it('preserves existing headers on patchJson', async () => {
let additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj: ifm.ITypedResponse<any> = await _http.patchJson<any>( let jsonObj = await _http.patchJson<any>(
'https://httpbin.org/patch', 'https://httpbin.org/patch',
{}, {},
additionalHeaders additionalHeaders
@@ -97,7 +98,7 @@ describe('headers', () => {
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
let httpWithHeaders = new httpm.HttpClient() const httpWithHeaders = new httpm.HttpClient()
httpWithHeaders.requestOptions = { httpWithHeaders.requestOptions = {
headers: { headers: {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
@@ -1,4 +1,4 @@
import * as httpm from '../_out' import * as httpm from '../lib'
describe('basics', () => { describe('basics', () => {
let _http: httpm.HttpClient let _http: httpm.HttpClient
@@ -11,69 +11,63 @@ describe('basics', () => {
_http.dispose() _http.dispose()
}) })
it('does basic http get request with keepAlive true', async done => { it('does basic http get request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
done()
}) })
it('does basic head request with keepAlive true', async done => { it('does basic head request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.head( const res: httpm.HttpClientResponse = await _http.head(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
done()
}) })
it('does basic http delete request with keepAlive true', async done => { it('does basic http delete request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.del( const res: httpm.HttpClientResponse = await _http.del(
'http://httpbin.org/delete' 'http://httpbin.org/delete'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) JSON.parse(body)
done()
}) })
it('does basic http post request with keepAlive true', async done => { it('does basic http post request with keepAlive true', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.post( const res: httpm.HttpClientResponse = await _http.post(
'http://httpbin.org/post', 'http://httpbin.org/post',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/post') expect(obj.url).toBe('http://httpbin.org/post')
done()
}) })
it('does basic http patch request with keepAlive true', async done => { it('does basic http patch request with keepAlive true', async () => {
let b: string = 'Hello World!' const b = 'Hello World!'
let res: httpm.HttpClientResponse = await _http.patch( const res: httpm.HttpClientResponse = await _http.patch(
'http://httpbin.org/patch', 'http://httpbin.org/patch',
b b
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.data).toBe(b) expect(obj.data).toBe(b)
expect(obj.url).toBe('http://httpbin.org/patch') expect(obj.url).toBe('http://httpbin.org/patch')
done()
}) })
it('does basic http options request with keepAlive true', async done => { it('does basic http options request with keepAlive true', async () => {
let res: httpm.HttpClientResponse = await _http.options( const res: httpm.HttpClientResponse = await _http.options(
'http://httpbin.org' 'http://httpbin.org'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() await res.readBody()
done()
}) })
}) })
+43 -39
View File
@@ -1,18 +1,20 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import * as http from 'http' import * as http from 'http'
import * as httpm from '../_out' import * as httpm from '../lib/'
import * as pm from '../_out/proxy' import * as pm from '../lib/proxy'
import * as proxy from 'proxy' // eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
import * as tunnelm from 'tunnel' const proxy = require('proxy')
let _proxyConnects: string[] let _proxyConnects: string[]
let _proxyServer: http.Server let _proxyServer: http.Server
let _proxyUrl = 'http://127.0.0.1:8080' const _proxyUrl = 'http://127.0.0.1:8080'
describe('proxy', () => { describe('proxy', () => {
beforeAll(async () => { beforeAll(async () => {
// Start proxy server // Start proxy server
_proxyServer = proxy() _proxyServer = proxy()
await new Promise(resolve => { await new Promise<void>(resolve => {
const port = Number(_proxyUrl.split(':')[2]) const port = Number(_proxyUrl.split(':')[2])
_proxyServer.listen(port, () => resolve()) _proxyServer.listen(port, () => resolve())
}) })
@@ -32,126 +34,126 @@ describe('proxy', () => {
_clearVars() _clearVars()
// Stop proxy server // Stop proxy server
await new Promise(resolve => { await new Promise<void>(resolve => {
_proxyServer.once('close', () => resolve()) _proxyServer.once('close', () => resolve())
_proxyServer.close() _proxyServer.close()
}) })
}) })
it('getProxyUrl does not return proxyUrl if variables not set', () => { it('getProxyUrl does not return proxyUrl if variables not set', () => {
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => { it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => { it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
process.env['http_proxy'] = 'https://myproxysvr' process.env['http_proxy'] = 'https://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => { it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com')) const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => { it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://myserver')) const proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => { it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
process.env['https_proxy'] = 'https://myproxysvr' process.env['https_proxy'] = 'https://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('https://github.com')) const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => { it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://myserver')) const proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
expect(proxyUrl).toBeUndefined() expect(proxyUrl).toBeUndefined()
}) })
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => { it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
process.env['http_proxy'] = 'http://myproxysvr' process.env['http_proxy'] = 'http://myproxysvr'
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let proxyUrl = pm.getProxyUrl(new URL('http://github.com')) const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
expect(proxyUrl).toBeDefined() expect(proxyUrl).toBeDefined()
}) })
it('checkBypass returns true if host as no_proxy list', () => { it('checkBypass returns true if host as no_proxy list', () => {
process.env['no_proxy'] = 'myserver' process.env['no_proxy'] = 'myserver'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list', () => { it('checkBypass returns true if host in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080' process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with spaces', () => { it('checkBypass returns true if host in no_proxy list with spaces', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with port', () => { it('checkBypass returns true if host in no_proxy list with port', () => {
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080')) const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host with port in no_proxy list without port', () => { it('checkBypass returns true if host with port in no_proxy list without port', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver:8080')) const bypass = pm.checkBypass(new URL('https://myserver:8080'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with default https port', () => { it('checkBypass returns true if host in no_proxy list with default https port', () => {
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
let bypass = pm.checkBypass(new URL('https://myserver')) const bypass = pm.checkBypass(new URL('https://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns true if host in no_proxy list with default http port', () => { it('checkBypass returns true if host in no_proxy list with default http port', () => {
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver' process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
let bypass = pm.checkBypass(new URL('http://myserver')) const bypass = pm.checkBypass(new URL('http://myserver'))
expect(bypass).toBeTruthy() expect(bypass).toBeTruthy()
}) })
it('checkBypass returns false if host not in no_proxy list', () => { it('checkBypass returns false if host not in no_proxy list', () => {
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080' process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
let bypass = pm.checkBypass(new URL('https://github.com')) const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy() expect(bypass).toBeFalsy()
}) })
it('checkBypass returns false if empty no_proxy', () => { it('checkBypass returns false if empty no_proxy', () => {
process.env['no_proxy'] = '' process.env['no_proxy'] = ''
let bypass = pm.checkBypass(new URL('https://github.com')) const bypass = pm.checkBypass(new URL('https://github.com'))
expect(bypass).toBeFalsy() expect(bypass).toBeFalsy()
}) })
it('HttpClient does basic http get request through proxy', async () => { it('HttpClient does basic http get request through proxy', async () => {
process.env['http_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:80']) expect(_proxyConnects).toEqual(['httpbin.org:80'])
}) })
@@ -160,12 +162,12 @@ describe('proxy', () => {
process.env['http_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org' process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'http://httpbin.org/get' 'http://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('http://httpbin.org/get') expect(obj.url).toBe('http://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -173,12 +175,12 @@ describe('proxy', () => {
it('HttpClient does basic https get request through proxy', async () => { it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toEqual(['httpbin.org:443']) expect(_proxyConnects).toEqual(['httpbin.org:443'])
}) })
@@ -187,12 +189,12 @@ describe('proxy', () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
process.env['no_proxy'] = 'httpbin.org' process.env['no_proxy'] = 'httpbin.org'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://httpbin.org/get' 'https://httpbin.org/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
let body: string = await res.readBody() const body: string = await res.readBody()
let obj: any = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://httpbin.org/get') expect(obj.url).toBe('https://httpbin.org/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -200,7 +202,8 @@ describe('proxy', () => {
it('proxyAuth not set in tunnel agent when authentication is not provided', async () => { it('proxyAuth not set in tunnel agent when authentication is not provided', async () => {
process.env['https_proxy'] = 'http://127.0.0.1:8080' process.env['https_proxy'] = 'http://127.0.0.1:8080'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url') const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent) console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1') expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080') expect(agent.proxyOptions.port).toBe('8080')
@@ -210,7 +213,8 @@ describe('proxy', () => {
it('proxyAuth is set in tunnel agent when authentication is provided', async () => { it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
process.env['https_proxy'] = 'http://user:[email protected]:8080' process.env['https_proxy'] = 'http://user:[email protected]:8080'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
let agent: tunnelm.TunnelingAgent = httpClient.getAgent('https://some-url') const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent) console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1') expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080') expect(agent.proxyOptions.port).toBe('8080')
@@ -218,7 +222,7 @@ describe('proxy', () => {
}) })
}) })
function _clearVars() { function _clearVars(): void {
delete process.env.http_proxy delete process.env.http_proxy
delete process.env.HTTP_PROXY delete process.env.HTTP_PROXY
delete process.env.https_proxy delete process.env.https_proxy
+117 -10279
View File
File diff suppressed because it is too large Load Diff
+32 -25
View File
@@ -1,39 +1,46 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "1.0.11", "version": "2.0.0",
"description": "Actions Http Client", "description": "Actions Http Client",
"main": "index.js", "keywords": [
"scripts": { "github",
"build": "rm -Rf ./_out && tsc && cp package*.json ./_out && cp *.md ./_out && cp LICENSE ./_out && cp actions.png ./_out", "actions",
"test": "jest", "http"
"format": "prettier --write *.ts && prettier --write **/*.ts", ],
"format-check": "prettier --check *.ts && prettier --check **/*.ts", "homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
"audit-check": "npm audit --audit-level=moderate" "license": "MIT",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"lib": "lib",
"test": "__tests__"
},
"files": [
"lib",
"!.DS_Store"
],
"publishConfig": {
"access": "public"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/actions/http-client.git" "url": "git+https://github.com/actions/toolkit.git",
"directory": "packages/github"
},
"scripts": {
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
"test": "echo \"Error: run tests from root\" && exit 1",
"build": "tsc",
"format": "prettier --write **/*.ts",
"format-check": "prettier --check **/*.ts",
"tsc": "tsc"
}, },
"keywords": [
"Actions",
"Http"
],
"author": "GitHub, Inc.",
"license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/actions/http-client/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"homepage": "https://github.com/actions/http-client#readme",
"devDependencies": { "devDependencies": {
"@types/jest": "^25.1.4", "@types/tunnel": "0.0.3",
"@types/node": "^12.12.31",
"jest": "^25.1.0",
"prettier": "^2.0.4",
"proxy": "^1.0.1", "proxy": "^1.0.1",
"ts-jest": "^25.2.1",
"typescript": "^3.8.3"
},
"dependencies": {
"tunnel": "0.0.6" "tunnel": "0.0.6"
} }
} }
+34 -34
View File
@@ -1,6 +1,8 @@
import ifm = require('./interfaces') import * as http from 'http'
import * as ifm from './interfaces'
import {HttpClientResponse} from './index'
export class BasicCredentialHandler implements ifm.IRequestHandler { export class BasicCredentialHandler implements ifm.RequestHandler {
username: string username: string
password: string password: string
@@ -9,27 +11,26 @@ export class BasicCredentialHandler implements ifm.IRequestHandler {
this.password = password this.password = password
} }
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = if (!options.headers) {
'Basic ' + throw Error('The request has no headers')
Buffer.from(this.username + ':' + this.password).toString('base64') }
options.headers['Authorization'] = `Basic ${Buffer.from(
`${this.username}:${this.password}`
).toString('base64')}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
export class BearerCredentialHandler implements ifm.IRequestHandler { export class BearerCredentialHandler implements ifm.RequestHandler {
token: string token: string
constructor(token: string) { constructor(token: string) {
@@ -38,26 +39,25 @@ export class BearerCredentialHandler implements ifm.IRequestHandler {
// currently implements pre-authorization // currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401 // TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = 'Bearer ' + this.token if (!options.headers) {
throw Error('The request has no headers')
}
options.headers['Authorization'] = `Bearer ${this.token}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
export class PersonalAccessTokenCredentialHandler export class PersonalAccessTokenCredentialHandler
implements ifm.IRequestHandler { implements ifm.RequestHandler {
token: string token: string
constructor(token: string) { constructor(token: string) {
@@ -66,21 +66,21 @@ export class PersonalAccessTokenCredentialHandler
// currently implements pre-authorization // currently implements pre-authorization
// TODO: support preAuth = false where it hooks on 401 // TODO: support preAuth = false where it hooks on 401
prepareRequest(options: any): void { prepareRequest(options: http.RequestOptions): void {
options.headers['Authorization'] = if (!options.headers) {
'Basic ' + Buffer.from('PAT:' + this.token).toString('base64') throw Error('The request has no headers')
}
options.headers['Authorization'] = `Basic ${Buffer.from(
`PAT:${this.token}`
).toString('base64')}`
} }
// This handler cannot handle 401 // This handler cannot handle 401
canHandleAuthentication(response: ifm.IHttpClientResponse): boolean { canHandleAuthentication(): boolean {
return false return false
} }
handleAuthentication( async handleAuthentication(): Promise<HttpClientResponse> {
httpClient: ifm.IHttpClient, throw new Error('not implemented')
requestInfo: ifm.IRequestInfo,
objs
): Promise<ifm.IHttpClientResponse> {
return null
} }
} }
+181 -176
View File
@@ -1,9 +1,11 @@
import http = require('http') /* eslint-disable @typescript-eslint/no-explicit-any */
import https = require('https')
import ifm = require('./interfaces')
import pm = require('./proxy')
let tunnel: any import * as http from 'http'
import * as https from 'https'
import * as ifm from './interfaces'
import * as net from 'net'
import * as pm from './proxy'
import * as tunnel from 'tunnel'
export enum HttpCodes { export enum HttpCodes {
OK = 200, OK = 200,
@@ -49,7 +51,7 @@ export enum MediaTypes {
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
export function getProxyUrl(serverUrl: string): string { export function getProxyUrl(serverUrl: string): string {
let proxyUrl = pm.getProxyUrl(new URL(serverUrl)) const proxyUrl = pm.getProxyUrl(new URL(serverUrl))
return proxyUrl ? proxyUrl.href : '' return proxyUrl ? proxyUrl.href : ''
} }
@@ -77,18 +79,18 @@ export class HttpClientError extends Error {
Object.setPrototypeOf(this, HttpClientError.prototype) Object.setPrototypeOf(this, HttpClientError.prototype)
} }
public statusCode: number statusCode: number
public result?: any result?: any
} }
export class HttpClientResponse implements ifm.IHttpClientResponse { export class HttpClientResponse {
constructor(message: http.IncomingMessage) { constructor(message: http.IncomingMessage) {
this.message = message this.message = message
} }
public message: http.IncomingMessage message: http.IncomingMessage
readBody(): Promise<string> { async readBody(): Promise<string> {
return new Promise<string>(async (resolve, reject) => { return new Promise<string>(async resolve => {
let output = Buffer.alloc(0) let output = Buffer.alloc(0)
this.message.on('data', (chunk: Buffer) => { this.message.on('data', (chunk: Buffer) => {
@@ -102,32 +104,32 @@ export class HttpClientResponse implements ifm.IHttpClientResponse {
} }
} }
export function isHttps(requestUrl: string) { export function isHttps(requestUrl: string): boolean {
let parsedUrl: URL = new URL(requestUrl) const parsedUrl: URL = new URL(requestUrl)
return parsedUrl.protocol === 'https:' return parsedUrl.protocol === 'https:'
} }
export class HttpClient { export class HttpClient {
userAgent: string | undefined userAgent: string | undefined
handlers: ifm.IRequestHandler[] handlers: ifm.RequestHandler[]
requestOptions: ifm.IRequestOptions requestOptions: ifm.RequestOptions | undefined
private _ignoreSslError: boolean = false private _ignoreSslError = false
private _socketTimeout: number private _socketTimeout: number | undefined
private _allowRedirects: boolean = true private _allowRedirects = true
private _allowRedirectDowngrade: boolean = false private _allowRedirectDowngrade = false
private _maxRedirects: number = 50 private _maxRedirects = 50
private _allowRetries: boolean = false private _allowRetries = false
private _maxRetries: number = 1 private _maxRetries = 1
private _agent private _agent: any
private _proxyAgent private _proxyAgent: any
private _keepAlive: boolean = false private _keepAlive = false
private _disposed: boolean = false private _disposed = false
constructor( constructor(
userAgent?: string, userAgent?: string,
handlers?: ifm.IRequestHandler[], handlers?: ifm.RequestHandler[],
requestOptions?: ifm.IRequestOptions requestOptions?: ifm.RequestOptions
) { ) {
this.userAgent = userAgent this.userAgent = userAgent
this.handlers = handlers || [] this.handlers = handlers || []
@@ -165,64 +167,64 @@ export class HttpClient {
} }
} }
public options( async options(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {}) return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
} }
public get( async get(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('GET', requestUrl, null, additionalHeaders || {}) return this.request('GET', requestUrl, null, additionalHeaders || {})
} }
public del( async del(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('DELETE', requestUrl, null, additionalHeaders || {}) return this.request('DELETE', requestUrl, null, additionalHeaders || {})
} }
public post( async post(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('POST', requestUrl, data, additionalHeaders || {}) return this.request('POST', requestUrl, data, additionalHeaders || {})
} }
public patch( async patch(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('PATCH', requestUrl, data, additionalHeaders || {}) return this.request('PATCH', requestUrl, data, additionalHeaders || {})
} }
public put( async put(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('PUT', requestUrl, data, additionalHeaders || {}) return this.request('PUT', requestUrl, data, additionalHeaders || {})
} }
public head( async head(
requestUrl: string, requestUrl: string,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request('HEAD', requestUrl, null, additionalHeaders || {}) return this.request('HEAD', requestUrl, null, additionalHeaders || {})
} }
public sendStream( async sendStream(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
additionalHeaders?: ifm.IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return this.request(verb, requestUrl, stream, additionalHeaders) return this.request(verb, requestUrl, stream, additionalHeaders)
} }
@@ -230,28 +232,28 @@ export class HttpClient {
* Gets a typed object from an endpoint * Gets a typed object from an endpoint
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
*/ */
public async getJson<T>( async getJson<T>(
requestUrl: string, requestUrl: string,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.get( const res: HttpClientResponse = await this.get(
requestUrl, requestUrl,
additionalHeaders additionalHeaders
) )
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async postJson<T>( async postJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -262,7 +264,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.post( const res: HttpClientResponse = await this.post(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -270,12 +272,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async putJson<T>( async putJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -286,7 +288,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.put( const res: HttpClientResponse = await this.put(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -294,12 +296,12 @@ export class HttpClient {
return this._processResponse<T>(res, this.requestOptions) return this._processResponse<T>(res, this.requestOptions)
} }
public async patchJson<T>( async patchJson<T>(
requestUrl: string, requestUrl: string,
obj: any, obj: any,
additionalHeaders: ifm.IHeaders = {} additionalHeaders: http.OutgoingHttpHeaders = {}
): Promise<ifm.ITypedResponse<T>> { ): Promise<ifm.TypedResponse<T>> {
let data: string = JSON.stringify(obj, null, 2) const data: string = JSON.stringify(obj, null, 2)
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader( additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
additionalHeaders, additionalHeaders,
Headers.Accept, Headers.Accept,
@@ -310,7 +312,7 @@ export class HttpClient {
Headers.ContentType, Headers.ContentType,
MediaTypes.ApplicationJson MediaTypes.ApplicationJson
) )
let res: ifm.IHttpClientResponse = await this.patch( const res: HttpClientResponse = await this.patch(
requestUrl, requestUrl,
data, data,
additionalHeaders additionalHeaders
@@ -323,28 +325,28 @@ export class HttpClient {
* All other methods such as get, post, patch, and request ultimately call this. * All other methods such as get, post, patch, and request ultimately call this.
* Prefer get, del, post and patch * Prefer get, del, post and patch
*/ */
public async request( async request(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream | null,
headers: ifm.IHeaders headers?: http.OutgoingHttpHeaders
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
if (this._disposed) { if (this._disposed) {
throw new Error('Client has already been disposed.') throw new Error('Client has already been disposed.')
} }
let parsedUrl = new URL(requestUrl) const parsedUrl = new URL(requestUrl)
let info: ifm.IRequestInfo = this._prepareRequest(verb, parsedUrl, headers) let info: ifm.RequestInfo = this._prepareRequest(verb, parsedUrl, headers)
// Only perform retries on reads since writes may not be idempotent. // Only perform retries on reads since writes may not be idempotent.
let maxTries: number = const maxTries: number =
this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1 this._allowRetries && RetryableHttpVerbs.includes(verb)
? this._maxRetries + 1 ? this._maxRetries + 1
: 1 : 1
let numTries: number = 0 let numTries = 0
let response: HttpClientResponse let response: HttpClientResponse | undefined
while (numTries < maxTries) { do {
response = await this.requestRaw(info, data) response = await this.requestRaw(info, data)
// Check if it's an authentication challenge // Check if it's an authentication challenge
@@ -353,11 +355,11 @@ export class HttpClient {
response.message && response.message &&
response.message.statusCode === HttpCodes.Unauthorized response.message.statusCode === HttpCodes.Unauthorized
) { ) {
let authenticationHandler: ifm.IRequestHandler let authenticationHandler: ifm.RequestHandler | undefined
for (let i = 0; i < this.handlers.length; i++) { for (const handler of this.handlers) {
if (this.handlers[i].canHandleAuthentication(response)) { if (handler.canHandleAuthentication(response)) {
authenticationHandler = this.handlers[i] authenticationHandler = handler
break break
} }
} }
@@ -373,19 +375,21 @@ export class HttpClient {
let redirectsRemaining: number = this._maxRedirects let redirectsRemaining: number = this._maxRedirects
while ( while (
HttpRedirectCodes.indexOf(response.message.statusCode) != -1 && response.message.statusCode &&
HttpRedirectCodes.includes(response.message.statusCode) &&
this._allowRedirects && this._allowRedirects &&
redirectsRemaining > 0 redirectsRemaining > 0
) { ) {
const redirectUrl: string | null = response.message.headers['location'] const redirectUrl: string | undefined =
response.message.headers['location']
if (!redirectUrl) { if (!redirectUrl) {
// if there's no location to redirect to, we won't // if there's no location to redirect to, we won't
break break
} }
let parsedRedirectUrl = new URL(redirectUrl) const parsedRedirectUrl = new URL(redirectUrl)
if ( if (
parsedUrl.protocol == 'https:' && parsedUrl.protocol === 'https:' &&
parsedUrl.protocol != parsedRedirectUrl.protocol && parsedUrl.protocol !== parsedRedirectUrl.protocol &&
!this._allowRedirectDowngrade !this._allowRedirectDowngrade
) { ) {
throw new Error( throw new Error(
@@ -399,7 +403,7 @@ export class HttpClient {
// strip authorization header if redirected to a different hostname // strip authorization header if redirected to a different hostname
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) { if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
for (let header in headers) { for (const header in headers) {
// header names are case insensitive // header names are case insensitive
if (header.toLowerCase() === 'authorization') { if (header.toLowerCase() === 'authorization') {
delete headers[header] delete headers[header]
@@ -413,7 +417,10 @@ export class HttpClient {
redirectsRemaining-- redirectsRemaining--
} }
if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) { if (
!response.message.statusCode ||
!HttpResponseRetryCodes.includes(response.message.statusCode)
) {
// If not a retry code, return immediately instead of retrying // If not a retry code, return immediately instead of retrying
return response return response
} }
@@ -424,7 +431,7 @@ export class HttpClient {
await response.readBody() await response.readBody()
await this._performExponentialBackoff(numTries) await this._performExponentialBackoff(numTries)
} }
} } while (numTries < maxTries)
return response return response
} }
@@ -432,7 +439,7 @@ export class HttpClient {
/** /**
* Needs to be called if keepAlive is set to true in request options. * Needs to be called if keepAlive is set to true in request options.
*/ */
public dispose() { dispose(): void {
if (this._agent) { if (this._agent) {
this._agent.destroy() this._agent.destroy()
} }
@@ -445,21 +452,21 @@ export class HttpClient {
* @param info * @param info
* @param data * @param data
*/ */
public requestRaw( async requestRaw(
info: ifm.IRequestInfo, info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream data: string | NodeJS.ReadableStream | null
): Promise<ifm.IHttpClientResponse> { ): Promise<HttpClientResponse> {
return new Promise<ifm.IHttpClientResponse>((resolve, reject) => { return new Promise<HttpClientResponse>((resolve, reject) => {
let callbackForResult = function ( function callbackForResult(err?: Error, res?: HttpClientResponse): void {
err: any,
res: ifm.IHttpClientResponse
) {
if (err) { if (err) {
reject(err) reject(err)
} } else if (!res) {
// If `err` is not passed, then `res` must be passed.
reject(new Error('Unknown error'))
} else {
resolve(res) resolve(res)
} }
}
this.requestRawWithCallback(info, data, callbackForResult) this.requestRawWithCallback(info, data, callbackForResult)
}) })
@@ -471,33 +478,35 @@ export class HttpClient {
* @param data * @param data
* @param onResult * @param onResult
*/ */
public requestRawWithCallback( requestRawWithCallback(
info: ifm.IRequestInfo, info: ifm.RequestInfo,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream | null,
onResult: (err: any, res: ifm.IHttpClientResponse) => void onResult: (err?: Error, res?: HttpClientResponse) => void
): void { ): void {
let socket
if (typeof data === 'string') { if (typeof data === 'string') {
if (!info.options.headers) {
info.options.headers = {}
}
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8') info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
} }
let callbackCalled: boolean = false let callbackCalled = false
let handleResult = (err: any, res: HttpClientResponse) => { function handleResult(err?: Error, res?: HttpClientResponse): void {
if (!callbackCalled) { if (!callbackCalled) {
callbackCalled = true callbackCalled = true
onResult(err, res) onResult(err, res)
} }
} }
let req: http.ClientRequest = info.httpModule.request( const req: http.ClientRequest = info.httpModule.request(
info.options, info.options,
(msg: http.IncomingMessage) => { (msg: http.IncomingMessage) => {
let res: HttpClientResponse = new HttpClientResponse(msg) const res: HttpClientResponse = new HttpClientResponse(msg)
handleResult(null, res) handleResult(undefined, res)
} }
) )
let socket: net.Socket
req.on('socket', sock => { req.on('socket', sock => {
socket = sock socket = sock
}) })
@@ -507,13 +516,13 @@ export class HttpClient {
if (socket) { if (socket) {
socket.end() socket.end()
} }
handleResult(new Error('Request timeout: ' + info.options.path), null) handleResult(new Error(`Request timeout: ${info.options.path}`))
}) })
req.on('error', function(err) { req.on('error', function(err) {
// err has statusCode property // err has statusCode property
// res should have headers // res should have headers
handleResult(err, null) handleResult(err)
}) })
if (data && typeof data === 'string') { if (data && typeof data === 'string') {
@@ -536,17 +545,17 @@ export class HttpClient {
* routing through a proxy server - depending upon the url and proxy environment variables. * routing through a proxy server - depending upon the url and proxy environment variables.
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
*/ */
public getAgent(serverUrl: string): http.Agent { getAgent(serverUrl: string): http.Agent {
let parsedUrl = new URL(serverUrl) const parsedUrl = new URL(serverUrl)
return this._getAgent(parsedUrl) return this._getAgent(parsedUrl)
} }
private _prepareRequest( private _prepareRequest(
method: string, method: string,
requestUrl: URL, requestUrl: URL,
headers: ifm.IHeaders headers?: http.OutgoingHttpHeaders
): ifm.IRequestInfo { ): ifm.RequestInfo {
const info: ifm.IRequestInfo = <ifm.IRequestInfo>{} const info: ifm.RequestInfo = <ifm.RequestInfo>{}
info.parsedUrl = requestUrl info.parsedUrl = requestUrl
const usingSsl: boolean = info.parsedUrl.protocol === 'https:' const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
@@ -570,23 +579,22 @@ export class HttpClient {
// gives handlers an opportunity to participate // gives handlers an opportunity to participate
if (this.handlers) { if (this.handlers) {
this.handlers.forEach(handler => { for (const handler of this.handlers) {
handler.prepareRequest(info.options) handler.prepareRequest(info.options)
}) }
} }
return info return info
} }
private _mergeHeaders(headers: ifm.IHeaders): ifm.IHeaders { private _mergeHeaders(
const lowercaseKeys = obj => headers?: http.OutgoingHttpHeaders
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {}) ): http.OutgoingHttpHeaders {
if (this.requestOptions && this.requestOptions.headers) { if (this.requestOptions && this.requestOptions.headers) {
return Object.assign( return Object.assign(
{}, {},
lowercaseKeys(this.requestOptions.headers), lowercaseKeys(this.requestOptions.headers),
lowercaseKeys(headers) lowercaseKeys(headers || {})
) )
} }
@@ -594,14 +602,11 @@ export class HttpClient {
} }
private _getExistingOrDefaultHeader( private _getExistingOrDefaultHeader(
additionalHeaders: ifm.IHeaders, additionalHeaders: http.OutgoingHttpHeaders,
header: string, header: string,
_default: string _default: string
) { ): string | number | string[] {
const lowercaseKeys = obj => let clientHeader: string | undefined
Object.keys(obj).reduce((c, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
let clientHeader: string
if (this.requestOptions && this.requestOptions.headers) { if (this.requestOptions && this.requestOptions.headers) {
clientHeader = lowercaseKeys(this.requestOptions.headers)[header] clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
} }
@@ -610,8 +615,8 @@ export class HttpClient {
private _getAgent(parsedUrl: URL): http.Agent { private _getAgent(parsedUrl: URL): http.Agent {
let agent let agent
let proxyUrl: URL = pm.getProxyUrl(parsedUrl) const proxyUrl = pm.getProxyUrl(parsedUrl)
let useProxy = proxyUrl && proxyUrl.hostname const useProxy = proxyUrl && proxyUrl.hostname
if (this._keepAlive && useProxy) { if (this._keepAlive && useProxy) {
agent = this._proxyAgent agent = this._proxyAgent
@@ -622,24 +627,20 @@ export class HttpClient {
} }
// if agent is already assigned use that agent. // if agent is already assigned use that agent.
if (!!agent) { if (agent) {
return agent return agent
} }
const usingSsl = parsedUrl.protocol === 'https:' const usingSsl = parsedUrl.protocol === 'https:'
let maxSockets = 100 let maxSockets = 100
if (!!this.requestOptions) { if (this.requestOptions) {
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
} }
if (useProxy) { // This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
// If using proxy, need tunnel if (proxyUrl && proxyUrl.hostname) {
if (!tunnel) {
tunnel = require('tunnel')
}
const agentOptions = { const agentOptions = {
maxSockets: maxSockets, maxSockets,
keepAlive: this._keepAlive, keepAlive: this._keepAlive,
proxy: { proxy: {
...((proxyUrl.username || proxyUrl.password) && { ...((proxyUrl.username || proxyUrl.password) && {
@@ -664,7 +665,7 @@ export class HttpClient {
// if reusing agent across request and tunneling agent isn't assigned create a new agent // if reusing agent across request and tunneling agent isn't assigned create a new agent
if (this._keepAlive && !agent) { if (this._keepAlive && !agent) {
const options = {keepAlive: this._keepAlive, maxSockets: maxSockets} const options = {keepAlive: this._keepAlive, maxSockets}
agent = usingSsl ? new https.Agent(options) : new http.Agent(options) agent = usingSsl ? new https.Agent(options) : new http.Agent(options)
this._agent = agent this._agent = agent
} }
@@ -686,15 +687,35 @@ export class HttpClient {
return agent return agent
} }
private _performExponentialBackoff(retryNumber: number): Promise<void> { private async _performExponentialBackoff(retryNumber: number): Promise<void> {
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber) retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber) const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
return new Promise(resolve => setTimeout(() => resolve(), ms)) return new Promise(resolve => setTimeout(() => resolve(), ms))
} }
private static dateTimeDeserializer(key: any, value: any): any { private async _processResponse<T>(
res: HttpClientResponse,
options?: ifm.RequestOptions
): Promise<ifm.TypedResponse<T>> {
return new Promise<ifm.TypedResponse<T>>(async (resolve, reject) => {
const statusCode = res.message.statusCode || 0
const response: ifm.TypedResponse<T> = {
statusCode,
result: null,
headers: {}
}
// not found leads to null obj returned
if (statusCode === HttpCodes.NotFound) {
resolve(response)
}
// get the result from the body
function dateTimeDeserializer(key: any, value: any): any {
if (typeof value === 'string') { if (typeof value === 'string') {
let a = new Date(value) const a = new Date(value)
if (!isNaN(a.valueOf())) { if (!isNaN(a.valueOf())) {
return a return a
} }
@@ -703,33 +724,14 @@ export class HttpClient {
return value return value
} }
private async _processResponse<T>(
res: ifm.IHttpClientResponse,
options: ifm.IRequestOptions
): Promise<ifm.ITypedResponse<T>> {
return new Promise<ifm.ITypedResponse<T>>(async (resolve, reject) => {
const statusCode: number = res.message.statusCode
const response: ifm.ITypedResponse<T> = {
statusCode: statusCode,
result: null,
headers: {}
}
// not found leads to null obj returned
if (statusCode == HttpCodes.NotFound) {
resolve(response)
}
let obj: any let obj: any
let contents: string let contents: string | undefined
// get the result from the body
try { try {
contents = await res.readBody() contents = await res.readBody()
if (contents && contents.length > 0) { if (contents && contents.length > 0) {
if (options && options.deserializeDates) { if (options && options.deserializeDates) {
obj = JSON.parse(contents, HttpClient.dateTimeDeserializer) obj = JSON.parse(contents, dateTimeDeserializer)
} else { } else {
obj = JSON.parse(contents) obj = JSON.parse(contents)
} }
@@ -753,10 +755,10 @@ export class HttpClient {
// it may be the case that the exception is in the body message as string // it may be the case that the exception is in the body message as string
msg = contents msg = contents
} else { } else {
msg = 'Failed request: (' + statusCode + ')' msg = `Failed request: (${statusCode})`
} }
let err = new HttpClientError(msg, statusCode) const err = new HttpClientError(msg, statusCode)
err.result = response.result err.result = response.result
reject(err) reject(err)
@@ -766,3 +768,6 @@ export class HttpClient {
}) })
} }
} }
const lowercaseKeys = (obj: {[index: string]: any}): any =>
Object.keys(obj).reduce((c: any, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
+36 -43
View File
@@ -1,83 +1,76 @@
import http = require('http') import * as http from 'http'
import * as https from 'https'
import {HttpClientResponse} from './index'
export interface IHeaders { export interface HttpClient {
[key: string]: any
}
export interface IHttpClient {
options( options(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
get( get(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
del( del(
requestUrl: string, requestUrl: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
post( post(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
patch( patch(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
put( put(
requestUrl: string, requestUrl: string,
data: string, data: string,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
sendStream( sendStream(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
stream: NodeJS.ReadableStream, stream: NodeJS.ReadableStream,
additionalHeaders?: IHeaders additionalHeaders?: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
request( request(
verb: string, verb: string,
requestUrl: string, requestUrl: string,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream,
headers: IHeaders headers: http.OutgoingHttpHeaders
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
requestRaw( requestRaw(
info: IRequestInfo, info: RequestInfo,
data: string | NodeJS.ReadableStream data: string | NodeJS.ReadableStream
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
requestRawWithCallback( requestRawWithCallback(
info: IRequestInfo, info: RequestInfo,
data: string | NodeJS.ReadableStream, data: string | NodeJS.ReadableStream,
onResult: (err: any, res: IHttpClientResponse) => void onResult: (err?: Error, res?: HttpClientResponse) => void
): void ): void
} }
export interface IRequestHandler { export interface RequestHandler {
prepareRequest(options: http.RequestOptions): void prepareRequest(options: http.RequestOptions): void
canHandleAuthentication(response: IHttpClientResponse): boolean canHandleAuthentication(response: HttpClientResponse): boolean
handleAuthentication( handleAuthentication(
httpClient: IHttpClient, httpClient: HttpClient,
requestInfo: IRequestInfo, requestInfo: RequestInfo,
objs data: string | NodeJS.ReadableStream | null
): Promise<IHttpClientResponse> ): Promise<HttpClientResponse>
} }
export interface IHttpClientResponse { export interface RequestInfo {
message: http.IncomingMessage
readBody(): Promise<string>
}
export interface IRequestInfo {
options: http.RequestOptions options: http.RequestOptions
parsedUrl: URL parsedUrl: URL
httpModule: any httpModule: typeof http | typeof https
} }
export interface IRequestOptions { export interface RequestOptions {
headers?: IHeaders headers?: http.OutgoingHttpHeaders
socketTimeout?: number socketTimeout?: number
ignoreSslError?: boolean ignoreSslError?: boolean
allowRedirects?: boolean allowRedirects?: boolean
@@ -91,8 +84,8 @@ export interface IRequestOptions {
maxRetries?: number maxRetries?: number
} }
export interface ITypedResponse<T> { export interface TypedResponse<T> {
statusCode: number statusCode: number
result: T | null result: T | null
headers: Object headers: http.IncomingHttpHeaders
} }
+13 -13
View File
@@ -1,23 +1,23 @@
export function getProxyUrl(reqUrl: URL): URL | undefined { export function getProxyUrl(reqUrl: URL): URL | undefined {
let usingSsl = reqUrl.protocol === 'https:' const usingSsl = reqUrl.protocol === 'https:'
let proxyUrl: URL
if (checkBypass(reqUrl)) { if (checkBypass(reqUrl)) {
return proxyUrl return undefined
} }
let proxyVar: string const proxyVar = (() => {
if (usingSsl) { if (usingSsl) {
proxyVar = process.env['https_proxy'] || process.env['HTTPS_PROXY'] return process.env['https_proxy'] || process.env['HTTPS_PROXY']
} else { } else {
proxyVar = process.env['http_proxy'] || process.env['HTTP_PROXY'] return process.env['http_proxy'] || process.env['HTTP_PROXY']
} }
})()
if (proxyVar) { if (proxyVar) {
proxyUrl = new URL(proxyVar) return new URL(proxyVar)
} else {
return undefined
} }
return proxyUrl
} }
export function checkBypass(reqUrl: URL): boolean { export function checkBypass(reqUrl: URL): boolean {
@@ -25,13 +25,13 @@ export function checkBypass(reqUrl: URL): boolean {
return false return false
} }
let noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || '' const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
if (!noProxy) { if (!noProxy) {
return false return false
} }
// Determine the request port // Determine the request port
let reqPort: number let reqPort: number | undefined
if (reqUrl.port) { if (reqUrl.port) {
reqPort = Number(reqUrl.port) reqPort = Number(reqUrl.port)
} else if (reqUrl.protocol === 'http:') { } else if (reqUrl.protocol === 'http:') {
@@ -41,13 +41,13 @@ export function checkBypass(reqUrl: URL): boolean {
} }
// Format the request hostname and hostname with port // Format the request hostname and hostname with port
let upperReqHosts = [reqUrl.hostname.toUpperCase()] const upperReqHosts = [reqUrl.hostname.toUpperCase()]
if (typeof reqPort === 'number') { if (typeof reqPort === 'number') {
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`) upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
} }
// Compare request host against noproxy // Compare request host against noproxy
for (let upperNoProxyItem of noProxy for (const upperNoProxyItem of noProxy
.split(',') .split(',')
.map(x => x.trim().toUpperCase()) .map(x => x.trim().toUpperCase())
.filter(x => x)) { .filter(x => x)) {
+11
View File
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"rootDir": "./src",
"moduleResolution": "node"
},
"include": [
"./src"
]
}
+2 -2
View File
@@ -9,5 +9,5 @@ if [[ -z "$name" ]]; then
exit 1 exit 1
fi fi
lerna create @actions/$name npx lerna create @actions/$name
cp packages/toolkit/tsconfig.json packages/$name/tsconfig.json cp packages/core/tsconfig.json packages/$name/tsconfig.json