├── .babelrc ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ ├── nodejs.yml │ ├── publish.yml │ └── testCoverage.yml ├── .gitignore ├── .nvmrc ├── .travis.yml ├── LICENSE ├── README.md ├── package.json ├── playground.html ├── rollup.config.js ├── src └── main.js ├── test └── main.test.js ├── types └── main.d.ts └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["@babel/preset-env", { 4 | "targets": { 5 | "node": "8" 6 | } 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: leoek 2 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '39 18 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Test & Build 5 | 6 | on: 7 | push: 8 | branches: [ master, develop ] 9 | pull_request: 10 | branches: [ master, develop ] 11 | 12 | jobs: 13 | testAndBuild: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Read .nvmrc 18 | id: node_version 19 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc) 20 | - name: Setup node (with version from .nvmrc) 21 | uses: actions/setup-node@v1 22 | with: 23 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }} 24 | - run: yarn install --frozen-lockfile 25 | - run: yarn test 26 | - run: yarn build 27 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package when a release is created 2 | 3 | name: Test, Build & Publish 4 | 5 | on: 6 | release: 7 | types: [created] 8 | 9 | jobs: 10 | 11 | publish-npm: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Read .nvmrc 16 | id: node_version 17 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc) 18 | - name: Setup node (with version from .nvmrc) 19 | uses: actions/setup-node@v1 20 | with: 21 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }} 22 | registry-url: https://registry.npmjs.org/ 23 | - run: yarn install --frozen-lockfile 24 | - run: yarn test 25 | - run: yarn build 26 | - run: yarn publish 27 | env: 28 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 29 | 30 | -------------------------------------------------------------------------------- /.github/workflows/testCoverage.yml: -------------------------------------------------------------------------------- 1 | name: Test & Report Coverage 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | uploadCodeCov: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | - name: Read .nvmrc 15 | id: node_version 16 | run: echo ::set-output name=NODE_VERSION::$(cat .nvmrc) 17 | - name: Setup node (with version from .nvmrc) 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ steps.node_version.outputs.NODE_VERSION }} 21 | - name: install dev dependencies 22 | run: yarn install --frozen-lockfile 23 | - name: install codecov 24 | run: yarn add --dev codecov 25 | - name: create coverage report 26 | run: yarn test --coverage 27 | - name: upload coverage report to codecov 28 | run: yarn codecov --token="${{ secrets.CODECOV_TOKEN }}" 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | yarn-error.log 4 | coverage 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12.10.0 -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | - "8" 5 | - "6" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Leonard Krause 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) 2 | ![Tests & Build](https://github.com/leoek/fetch-to-curl/workflows/Test%20&%20Build/badge.svg) 3 | [![codecov](https://codecov.io/gh/leoek/fetch-to-curl/branch/master/graph/badge.svg?token=K9L7Q3K1CL)](https://codecov.io/gh/leoek/fetch-to-curl) 4 | ![Dependencies](https://img.shields.io/badge/Dependencies-%200%20-44be16) 5 | 6 | 7 | 8 | # fetch request to curl 9 | 10 | This module was inspired by [http-to-curl](https://github.com/drgx/http-to-curl). Use it to generate curl requests with the inputs you would usually use for javascripts fetch. However it does not patch any modules like http-to-curl. It is just a wrapper to generate the curl string. This ensures that there are no side effects with your actual usage of fetch. 11 | 12 | Also note that the goal of this library is to be as simple and minimal as possible. This also means that there are zero dependencies :tada: 13 | 14 | ## Installation 15 | 16 | ```sh 17 | yarn add fetch-to-curl 18 | ``` 19 | 20 | or 21 | 22 | ```sh 23 | npm install fetch-to-curl 24 | ``` 25 | 26 | ## Usage 27 | 28 | ```js 29 | import { fetchToCurl } from 'fetch-to-curl'; 30 | // or In case there is no support for Es Modules in your environment: 31 | // const { fetchToCurl } = require("fetch-to-curl") 32 | 33 | const url = 'https://jsonplaceholder.typicode.com/posts/1'; 34 | const options = { 35 | headers: { 36 | Authorization: "BASIC SOMEBASE64STRING" 37 | }, 38 | method: 'get' 39 | }; 40 | // Log yopur request 41 | console.log(fetchToCurl(url, options)); 42 | // Do your request 43 | fetch(url, options); 44 | 45 | // Output 46 | curl "https://jsonplaceholder.typicode.com/posts/1" -X GET -H "Authorization: BASIC SOMEBASE64STRING" 47 | 48 | // You can also pass a single Request object 49 | console.log(fetchToCurl({ 50 | url: "https://jsonplaceholder.typicode.com/posts/1" 51 | headers: { 52 | Authorization: "BASIC SOMEBASE64STRING" 53 | }, 54 | method: 'get' 55 | })); 56 | 57 | // and/or a Headers object as you would to with fetch 58 | console.log(fetchToCurl({ 59 | url: "https://jsonplaceholder.typicode.com/posts/1" 60 | headers: new Headers({ 61 | Authorization: "BASIC SOMEBASE64STRING" 62 | }), 63 | method: 'get' 64 | })) 65 | 66 | ``` 67 | 68 | ## Playground and usage without package manager 69 | 70 | There is a minimal example of usage without package manager available which allows to [directly test this in the browser console](https://leoek.github.io/fetch-to-curl/playground.html). [(view source - playground.html)](https://github.com/leoek/fetch-to-curl/blob/master/playground.html) 71 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fetch-to-curl", 3 | "version": "0.6.0", 4 | "description": "Convert fetch HTTP request to curl", 5 | "main": "lib/bundle.js", 6 | "author": "Leonard Krause", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "rollup -c", 10 | "test": "jest" 11 | }, 12 | "files": [ 13 | "lib/bundle.js", 14 | "types" 15 | ], 16 | "types": "types/main.d.ts", 17 | "dependencies": {}, 18 | "devDependencies": { 19 | "@babel/core": "^7.12.10", 20 | "@babel/preset-env": "^7.12.11", 21 | "jest": "^26.6.3", 22 | "rollup": "^2.35.1", 23 | "rollup-plugin-babel-minify": "^10.0.0", 24 | "rollup-plugin-terser": "^7.0.2" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/leoek/fetch-to-curl.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/leoek/fetch-to-curl/issues" 32 | }, 33 | "homepage": "https://github.com/leoek/fetch-to-curl#readme", 34 | "keywords": [ 35 | "curl", 36 | "fetch", 37 | "http", 38 | "request-http", 39 | "debug", 40 | "react-native" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /playground.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 14 | 18 | 34 | 35 | 36 | 37 |

fetch to curl Playground

38 |

39 | Usually this library should be used together with a package manager like 40 | yarn or 41 | npm. (please 42 | refer to the 43 | README 46 | for general usage information) 47 |

48 |

49 | Note the creation of the exports object in line 11 of this html file, 50 | which mitigates the missing module support of the browser. 51 |

52 |

How to use this playground

53 |
  • Check your browser console (usually opens with F12)
  • 54 |
  • 55 | Try 56 | fetchToCurl("https://example.com", { headers: { "x-test": "test" 58 | }}) 60 | or 61 | console.log(fetchToCurl("https://example.com", { headers: { "x-test": 63 | "test" }})) 65 | in your browser console 66 |
  • 67 |
  • 68 | Note that the you can compare with the real request in the network tab. 69 |
  • 70 | 71 | 72 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import minify from 'rollup-plugin-babel-minify'; 2 | import { terser } from "rollup-plugin-terser"; 3 | 4 | export default { 5 | input: 'src/main.js', 6 | output: { 7 | file: 'lib/bundle.js', 8 | format: 'cjs', 9 | }, 10 | plugins: [minify(), terser()], 11 | }; 12 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * see https://fetch.spec.whatwg.org/#methods 3 | * 4 | * @export 5 | * @param {any} options 6 | * @returns {string} 7 | */ 8 | export const generateMethod = (options) => { 9 | const method = options.method; 10 | if (!method) return ''; 11 | const type = { 12 | GET: ' -X GET', 13 | POST: ' -X POST', 14 | PUT: ' -X PUT', 15 | PATCH: ' -X PATCH', 16 | DELETE: ' -X DELETE', 17 | HEAD: ' -X HEAD', 18 | OPTIONS: ' -X OPTIONS' 19 | }; 20 | return type[method.toUpperCase()] || ''; 21 | } 22 | 23 | /** 24 | * @export 25 | * @param {any} val 26 | * @returns true if the envirtonment supports Headers and val is of instance Headers 27 | */ 28 | export const isInstanceOfHeaders = (val) => { 29 | if (typeof Headers !== "function"){ 30 | /** 31 | * Environment does not support the Headers constructor 32 | * old internet explorer? 33 | */ 34 | return false; 35 | } 36 | return val instanceof Headers; 37 | } 38 | 39 | /** 40 | * @typedef {Object} HeaderParams 41 | * @property {Boolean} isEncode - A flag which is set to true if the request should set the --compressed flag 42 | * @property {String} params - The header params as string 43 | */ 44 | 45 | const getHeaderString = (name, val) => ` -H "${name}: ${`${val}`.replace(/(\\|")/g, '\\$1')}"`; 46 | 47 | /** 48 | * @export 49 | * @param {object={}} options 50 | * @param {object|Headers} options.headers 51 | * @returns {HeaderParams} An Object with the header info 52 | */ 53 | export const generateHeader = (options = {}) => { 54 | const { headers } = options; 55 | let isEncode = false; 56 | let headerParam = ''; 57 | if (isInstanceOfHeaders(headers)){ 58 | headers.forEach((val, name) => { 59 | if (name.toLocaleLowerCase() !== 'content-length') { 60 | headerParam += getHeaderString(name, val); 61 | } 62 | if (name.toLocaleLowerCase() === 'accept-encoding'){ 63 | isEncode = true; 64 | } 65 | }) 66 | } else if (headers){ 67 | Object.keys(headers).map(name => { 68 | if (name.toLocaleLowerCase() !== 'content-length') { 69 | headerParam += getHeaderString(name, headers[name]); 70 | } 71 | if (name.toLocaleLowerCase() === 'accept-encoding') { 72 | isEncode = true; 73 | } 74 | }); 75 | } 76 | return { 77 | params: headerParam, 78 | isEncode, 79 | }; 80 | } 81 | 82 | /** 83 | * @export 84 | * @param {Object} body 85 | * @returns {string} 86 | */ 87 | export function escapeBody(body) { 88 | if (typeof body !== 'string') return body 89 | return body.replace(/'/g, `'\\''`) 90 | } 91 | 92 | /** 93 | * @export 94 | * @param {Object} body 95 | * @returns {string} 96 | */ 97 | export function generateBody(body) { 98 | if (!body) return ''; 99 | if (typeof body === "object"){ 100 | return ` --data-binary '${escapeBody(JSON.stringify(body))}'`; 101 | } 102 | return ` --data-binary '${escapeBody(body)}'`; 103 | } 104 | 105 | /** 106 | * @export 107 | * @param {boolean} isEncode 108 | * @return {string} 109 | */ 110 | export function generateCompress(isEncode) { 111 | return isEncode ? ' --compressed' : ''; 112 | } 113 | 114 | /** 115 | * @export 116 | * @param {string|object} requestInfo 117 | * @param {object={}} requestInit 118 | */ 119 | export const fetchToCurl = (requestInfo, requestInit) => { 120 | let url, options; 121 | /** 122 | * initialization with an empty object is done here to 123 | * keep everything backwards compatible to 0.4.0 and below 124 | */ 125 | if (typeof requestInfo === "string" || requestInfo instanceof URL) { 126 | url = requestInfo; 127 | options = requestInit || {}; 128 | } else { 129 | url = (requestInfo || {}).url 130 | options = requestInfo || {} 131 | } 132 | const { body } = options; 133 | const headers = generateHeader(options); 134 | return `curl '${url}'${generateMethod(options)}${headers.params || ''}${generateBody(body)}${generateCompress(headers.isEncode)}`; 135 | } 136 | 137 | export default fetchToCurl; 138 | -------------------------------------------------------------------------------- /test/main.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | isInstanceOfHeaders, 3 | generateMethod, 4 | generateHeader, 5 | generateBody, 6 | generateCompress, 7 | fetchToCurl 8 | } from '../src/main'; 9 | 10 | const checkGeneratedHeadersResult = (generated, expectedHeaders, expectedEncoding) => { 11 | expect(generated).toHaveProperty("isEncode"); 12 | expect(generated).toHaveProperty("params"); 13 | expect(generated.isEncode).toBe(expectedEncoding); 14 | expect(generated.params).toMatch(new RegExp('( -H ".*?: .*?")+')) 15 | Object.entries(expectedHeaders).forEach(([name, value]) => { 16 | expect(generated.params.includes(`-H "${name}: ${value}"`) || generated.params.includes(`-H "${name.toLowerCase()}: ${value}"`)).toBeTruthy(); 17 | }) 18 | } 19 | 20 | describe('Environment wich supports Headers', () => { 21 | test('isInstanceOfHeaders detects Headers object correctly', () => { 22 | expect(isInstanceOfHeaders(new Headers())).toBeTruthy(); 23 | }); 24 | test('isInstanceOfHeaders detects plain js object correctly', () => { 25 | expect(isInstanceOfHeaders({})).toBeFalsy(); 26 | }); 27 | test('isInstanceOfHeaders detects falsy values correctly', () => { 28 | expect(isInstanceOfHeaders(null)).toBeFalsy(); 29 | expect(isInstanceOfHeaders()).toBeFalsy(); 30 | }); 31 | }) 32 | 33 | 34 | describe('Environment wich does not support Headers', () => { 35 | const originalHeaders = Headers; 36 | beforeEach(() => { 37 | delete global.Headers 38 | }) 39 | afterEach(() => { 40 | global.Headers = originalHeaders; 41 | }) 42 | test('isInstanceOfHeaders returns false if Headers constructor is not available', () => { 43 | expect(isInstanceOfHeaders({})).toBeFalsy() 44 | }); 45 | }) 46 | 47 | describe('Generate method param', () => { 48 | test('No method', () => { 49 | expect(generateMethod({})).toEqual(''); 50 | }); 51 | test('POST', () => { 52 | const option = { 53 | method: 'post' 54 | }; 55 | expect(generateMethod(option)).toEqual(' -X POST'); 56 | }); 57 | test('PUT', () => { 58 | const option = { 59 | method: 'Put' 60 | }; 61 | expect(generateMethod(option)).toEqual(' -X PUT'); 62 | }); 63 | test('GET', () => { 64 | const option = { 65 | method: 'GET' 66 | }; 67 | expect(generateMethod(option)).toEqual(' -X GET'); 68 | }); 69 | test('PATCH', () => { 70 | const option = { 71 | method: 'PATCH' 72 | }; 73 | expect(generateMethod(option)).toEqual(' -X PATCH'); 74 | }); 75 | test('DELETE', () => { 76 | const option = { 77 | method: 'DELETE' 78 | }; 79 | expect(generateMethod(option)).toEqual(' -X DELETE'); 80 | }); 81 | test('HEAD', () => { 82 | const option = { 83 | method: 'HEAD' 84 | }; 85 | expect(generateMethod(option)).toEqual(' -X HEAD'); 86 | }); 87 | test('OPTIONS', () => { 88 | const option = { 89 | method: 'OPTIONS' 90 | }; 91 | expect(generateMethod(option)).toEqual(' -X OPTIONS'); 92 | }); 93 | test('Unknown method', () => { 94 | const option = { 95 | method: 'xxxx' 96 | }; 97 | expect(generateMethod(option)).toEqual(''); 98 | }); 99 | }); 100 | 101 | describe('Generate header param', () => { 102 | test('No Header Options', () => { 103 | expect(generateHeader()).toEqual({ 104 | isEncode: false, 105 | params: "" 106 | }); 107 | }); 108 | 109 | test('Empty Header Options', () => { 110 | expect(generateHeader({})).toEqual({ 111 | isEncode: false, 112 | params: "" 113 | }); 114 | }); 115 | 116 | const testHeaders = { 117 | Accept: 'application/json, text/plain, */*', 118 | 'User-Agent': 'axios/0.18.0', 119 | 'X-Test': "TestVal" 120 | } 121 | 122 | const testHeadersWithEncoding = { 123 | ...testHeaders, 124 | 'accept-encoding': 'gzip', 125 | } 126 | 127 | const testHeadersWithContentLength = { 128 | ...testHeaders, 129 | 'content-length': "12345" 130 | } 131 | 132 | test('correctly parses Headers from object without encoding', () => { 133 | checkGeneratedHeadersResult(generateHeader({ 134 | headers: testHeaders 135 | }), testHeaders, false) 136 | }); 137 | 138 | test('correctly parses Headers from object with encoding', () => { 139 | checkGeneratedHeadersResult(generateHeader({ 140 | headers: testHeadersWithEncoding 141 | }), testHeadersWithEncoding, true) 142 | }); 143 | 144 | test('omits content-length Header when parsing headers from object', () => { 145 | checkGeneratedHeadersResult(generateHeader({ 146 | headers: testHeadersWithContentLength 147 | }), testHeaders, false) 148 | }); 149 | 150 | test('correctly parses Headers without encoding', () => { 151 | checkGeneratedHeadersResult(generateHeader({ 152 | headers: new Headers(testHeaders) 153 | }), testHeaders, false) 154 | }); 155 | 156 | test('correctly parses Headers with encoding', () => { 157 | checkGeneratedHeadersResult(generateHeader({ 158 | headers: new Headers(testHeadersWithEncoding) 159 | }), testHeadersWithEncoding, true) 160 | }); 161 | 162 | test('omits content-length Header when parsing headers from Headers object', () => { 163 | checkGeneratedHeadersResult(generateHeader({ 164 | headers: new Headers(testHeadersWithContentLength) 165 | }), testHeaders, false) 166 | }); 167 | }); 168 | 169 | describe('Generate body param', () => { 170 | test('No Body', () => { 171 | expect(generateBody()).toEqual(''); 172 | }); 173 | test('String Body', () => { 174 | expect(generateBody('a')).toEqual(" --data-binary 'a'"); 175 | }); 176 | test('Number Body', () => { 177 | expect(generateBody(12345)).toEqual(" --data-binary '12345'"); 178 | }); 179 | test('Object Body', () => { 180 | const options = { 181 | test: 'test:', 182 | testNumber: 12345, 183 | testDate: new Date(1609251707077), 184 | testQuotes: `'test'` 185 | }; 186 | expect(generateBody(options)).toEqual( 187 | ` --data-binary '{"test":"test:","testNumber":12345,"testDate":"2020-12-29T14:21:47.077Z","testQuotes":"'\\''test'\\''"}'` 188 | ); 189 | }); 190 | }); 191 | 192 | describe('Generate Compress param', () => { 193 | test('No compression', () => { 194 | expect(generateCompress()).toEqual(''); 195 | }); 196 | test('Have compression', () => { 197 | expect(generateCompress(true)).toEqual(' --compressed'); 198 | }); 199 | }); 200 | 201 | describe('fetchToCurl', () => { 202 | test('url string and empty options', () => { 203 | expect( 204 | fetchToCurl('google.com', {}) 205 | ).toEqual("curl 'google.com'"); 206 | }); 207 | 208 | test('url object and empty options', () => { 209 | expect( 210 | fetchToCurl(new URL('https://google.com/'), {}) 211 | ).toEqual("curl 'https://google.com/'"); 212 | }); 213 | 214 | test('url string and no options', () => { 215 | expect( 216 | fetchToCurl('google.com') 217 | ).toEqual("curl 'google.com'"); 218 | }); 219 | 220 | test('url string and Request Object', () => { 221 | expect( 222 | fetchToCurl('google.com', { method: "POST" }) 223 | ).toEqual("curl 'google.com' -X POST"); 224 | }); 225 | 226 | test('Request Object only', () => { 227 | expect( 228 | fetchToCurl({ url: "google.com", method: "POST" }) 229 | ).toEqual("curl 'google.com' -X POST"); 230 | }); 231 | 232 | test('No Parameters', () => { 233 | expect( 234 | fetchToCurl() 235 | ).toEqual("curl 'undefined'"); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /types/main.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://microsoft.github.io/PowerBI-JavaScript/interfaces/_node_modules_typedoc_node_modules_typescript_lib_lib_dom_d_.headers.html 3 | */ 4 | export type HeadersInit = Headers | string[][] | Record; 5 | export class Headers implements Iterable<[string, string]> { 6 | constructor(init?: HeadersInit); 7 | append(name: string, value: string): void; 8 | forEach(callback: (value: string, name: string) => void): void; 9 | has(name: string): boolean; 10 | delete(name: string): void; 11 | get(name: string): string | null; 12 | set(name: string, value: string): void; 13 | 14 | // Iterable methods 15 | entries(): IterableIterator<[string, string]>; 16 | keys(): IterableIterator; 17 | values(): IterableIterator; 18 | [Symbol.iterator](): Iterator<[string, string]>; 19 | } 20 | 21 | export type Method = "GET" | "POST" | "DELETE" | "PATCH" | "PUT" | "HEAD" | "OPTIONS"; 22 | 23 | export interface FetchOptions { 24 | url?: string; 25 | method?: Method | string; 26 | headers?: HeadersInit; 27 | body?: any; 28 | [rest: string]: any 29 | } 30 | 31 | declare function fetchToCurl( 32 | requestInfo: string | FetchOptions, 33 | requestInit?: FetchOptions 34 | ): string; 35 | 36 | export default fetchToCurl; 37 | --------------------------------------------------------------------------------