├── .circleci └── config.yml ├── .gitignore ├── .node-version ├── .prettierrc ├── LICENSE ├── README.md ├── logo.png ├── package-lock.json ├── package.json ├── src ├── __tests__ │ ├── content-type.test.ts │ ├── default.test.ts │ ├── force-body.test.ts │ └── option-container.test.ts ├── adapter │ └── AxiosRequestConfigAdapter.ts ├── enum │ ├── CURL_OPTIONS.ts │ ├── HTTP_HEADER.ts │ ├── HTTP_HEADER_CONTENT_TYPE.ts │ └── HTTP_METHOD.ts ├── index.ts ├── interface │ ├── IR2CurlOptions.ts │ └── IRequestAdaptor.ts └── lib │ ├── BodyHelper.ts │ ├── CommonUtils.ts │ ├── CurlBuilder.ts │ ├── HeaderHelper.ts │ ├── OptionContainer.ts │ └── isEmpty.ts ├── tsconfig.json └── tslint.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/r2curl 5 | docker: 6 | - image: circleci/node:10.15.3 7 | steps: 8 | - checkout 9 | - run: 10 | name: npminstall 11 | command: npm install 12 | - run: 13 | name: tslint check 14 | command: npx tslint -c ./tslint.json "src/**/*.ts" 15 | - run: 16 | name: jest 17 | command: npm test 18 | - run: 19 | name: typescript compile & artifact 20 | command: npm pack 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | dist 64 | .npmrc 65 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | 12.18.3 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "parser": "typescript" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 유용우 / CX 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 | # r2curl 2 | 3 | [![npm version](https://badge.fury.io/js/r2curl.svg)](https://badge.fury.io/js/r2curl) [![Download Status](https://img.shields.io/npm/dw/r2curl.svg)](https://npmcharts.com/compare/r2curl?minimal=true) [![Github Star](https://img.shields.io/github/stars/uyu423/r2curl.svg?style=popout)](https://github.com/uyu423/r2curl) [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php) [![CircleCI](https://circleci.com/gh/uyu423/r2curl.svg?style=svg)](https://circleci.com/gh/uyu423/r2curl) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/f74cdea970d44550a0bff9319e467256)](https://www.codacy.com/app/uyu423/r2curl?utm_source=github.com&utm_medium=referral&utm_content=uyu423/r2curl&utm_campaign=Badge_Grade) [![Code Climate](https://codeclimate.com/github/uyu423/r2curl.svg)](https://codeclimate.com/github/uyu423/r2curl) [![Test Coverage](https://api.codeclimate.com/v1/badges/bb19fbd2394b545aefb2/test_coverage)](https://codeclimate.com/github/uyu423/r2curl/test_coverage) 4 | 5 |

6 | 7 |

8 | 9 | ## Background 10 | 11 | - [r2curl](https://github.com/uyu423/r2curl) was inspired by [@delirius325/axios-curlirize](https://github.com/delirius325/axios-curlirize). 12 | - axios-curlirize is very convenient. but works as a middleware for axios, and I think this part is black box logic 13 | - it contains potentially asynchronous concurrency issues and difficult-to-manage elements. 14 | - So I created a new 'Request to cURL' package that is completely independent of the dependencies of axios. 15 | 16 | ## Feature 17 | 18 | - Generates cURL commands completely independently from the outside of the request wrapper package. 19 | - Provides additional options involved in generating the cURL command. 20 | - It will be updated soon to be available in packages like [node-fetch](https://www.npmjs.com/package/node-fetch) or [request](https://www.npmjs.com/package/request). 21 | 22 | ## Roadmap 23 | 24 | - [x] [axios](https://www.npmjs.com/package/axios) 25 | - [x] AxiosRequestConfig 26 | - [x] AxiosResposne 27 | - [ ] [node-fetch](https://www.npmjs.com/package/node-fetch) 28 | - [ ] [request](https://www.npmjs.com/package/request) 29 | - [ ] ... 30 | 31 | ## Install 32 | 33 | ```bash 34 | npm install r2curl --save 35 | ``` 36 | 37 | ## Usage 38 | 39 | ### axios 40 | 41 | #### `AxiosResponse` 42 | 43 | ```typescript 44 | // if js, const r2curl = require('r2curl'); 45 | import r2curl from 'r2curl'; 46 | 47 | const response = await axios.get('https://google.com'); 48 | const curl = r2curl(response); 49 | 50 | console.log(curl); 51 | // stdout "curl -X GET 'https://google.com' -H 'Accept:application/json, text/plain, */*' -H 'User-Agent:axios/0.18.0'" 52 | ``` 53 | 54 | #### `AxiosRequestConfig` 55 | 56 | ```typescript 57 | // if js, const r2curl = require('r2curl'); 58 | import r2curl from 'r2curl'; 59 | 60 | // config as AxiosRequestConfig 61 | const config = { 62 | url: 'https://google.com', 63 | method: 'POST', 64 | data: { 65 | caller: 'curl tester', 66 | }, 67 | headers: { 68 | 'Content-Type': 'application/json', 69 | }, 70 | }; 71 | 72 | const curl = r2curl(reqeustConfig); 73 | console.log(curl); 74 | // stdout `curl -X POST 'https://google.com' -H 'Content-Type:application/json' --data '{"caller":"curl tester"}'` 75 | 76 | const response = await axios.request(config); 77 | ``` 78 | 79 | ### node-fetch 80 | 81 | - update soon (target 0.2.0) 82 | - see [github project board](https://github.com/uyu423/r2curl/projects/1) 83 | 84 | ### request 85 | 86 | - update soon (target 0.2.0) 87 | - see [github project board](https://github.com/uyu423/r2curl/projects/1) 88 | 89 | ## More `r2curl` Options 90 | 91 | ### `option.quote` 92 | 93 | - Determines the type of quota around the body and uri. 94 | - default is `single` 95 | 96 | ```typescript 97 | import r2curl from 'r2curl'; 98 | 99 | // option as IR2CurlOptions.ts 100 | const option = { 101 | /** Determines the type of quota around the body and uri. */ 102 | quote: 'double', 103 | }; 104 | 105 | const curl = r2curl(requestConfig, option); 106 | console.log(curl); 107 | ``` 108 | 109 | ### `option.defaultContentType` 110 | 111 | - Determines the default Content-Type header value for `POST` and `PUT` requests. 112 | - default is `application/json; charset=utf-8` 113 | - Type is `(enum) HTTP_HEADER_CONTENT_TYPE` | `string` | `false`; 114 | - If you give `(boolean) false` to `defaultContentType`, you can disable `Content-Type` Header. 115 | 116 | ```typescript 117 | import r2curl, { HTTP_HEADER_CONTENT_TYPE } from 'r2curl'; 118 | 119 | // const optionUsingEnum = { 120 | // defaultContentType: HTTP_HEADER_CONTENT_TYPE.TEXT, 121 | // }; 122 | const option = { 123 | defaultContentType: 'application/json5', 124 | } 125 | const request: AxiosRequestConfig = { url: 'https://google.com', method: 'POST' }; 126 | 127 | const curl = r2curl(config, option); 128 | console.log(curl); 129 | // output: curl -X POST 'https://google.com' -H 'Content-Type:application/json5 130 | ``` 131 | 132 | ### `option.forceBody` 133 | 134 | - Accept Body all HTTP Method. 135 | - By default, Body is not allowed in `GET` and `DELETE` methods. 136 | - However, some services such as ElasticSearch should be able to use the Body as a `GET` method. At this point, use this option to activate the Body. 137 | 138 | ```typescript 139 | import r2curl from 'r2curl'; 140 | 141 | const config: AxiosRequestConfig = { 142 | url: 'https://google.com', 143 | method: 'GET', 144 | data: { 145 | caller: 'https://github.com/uyu423/r2curl', 146 | sorry: true, 147 | }, 148 | }; 149 | 150 | const option = { 151 | forceBody: true, 152 | } 153 | 154 | const curl = r2curl(config, option); 155 | // output: 'curl -X GET \'https://google.com\' --data \'{"caller":"https://github.com/uyu423/r2curl","sorry":true}\'' 156 | ``` -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uyu423/r2curl/7b8f53b6b6b14131eee2731b69fdbc52a351a54a/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "r2curl", 3 | "version": "0.2.4", 4 | "description": "Node.js Request Wrapper (axios, fetch, ..) to cURL Command String", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist" 9 | ], 10 | "scripts": { 11 | "test": "npx jest --coverage", 12 | "test:log": "npx cross-env DEBUG=r2curl:* npx jest", 13 | "test:watch": "npx cross-env DEBUG=r2curl:tc:* npx jest --watch", 14 | "lint": "npx tslint -c ./tslint.json \"./src/**/*.ts\"", 15 | "clean": "npm run clean:dist && npm run clean:artifact", 16 | "clean:artifact": "rm -rf ./*.tgz && true", 17 | "clean:dist": "rm -rf dist && true", 18 | "build": "npm run lint && npm run clean:dist && tsc", 19 | "prepack": "npm run clean && tsc" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/uyu423/r2curl.git" 24 | }, 25 | "keywords": [ 26 | "curl", 27 | "axios", 28 | "debug" 29 | ], 30 | "author": "Yowu Yu ", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/uyu423/r2curl/issues" 34 | }, 35 | "homepage": "https://github.com/uyu423/r2curl#readme", 36 | "dependencies": { 37 | "debug": "^4.1.1" 38 | }, 39 | "devDependencies": { 40 | "@types/debug": "^4.1.5", 41 | "@types/jest": "^24.9.1", 42 | "@types/node": "^12.12.31", 43 | "@types/shelljs": "^0.8.7", 44 | "axios": "^0.19.2", 45 | "cross-env": "^5.2.1", 46 | "jest": "^24.9.0", 47 | "prettier": "^1.19.1", 48 | "shelljs": "^0.8.3", 49 | "ts-jest": "^24.3.0", 50 | "ts-loader": "^5.4.5", 51 | "ts-node": "^8.8.1", 52 | "tslint": "^5.20.1", 53 | "tslint-config-airbnb": "^5.11.2", 54 | "tslint-config-prettier": "^1.18.0", 55 | "typescript": "^3.8.3" 56 | }, 57 | "jest": { 58 | "testEnvironment": "node", 59 | "moduleFileExtensions": [ 60 | "ts", 61 | "tsx", 62 | "js" 63 | ], 64 | "transform": { 65 | "^.+\\.(ts|tsx)$": "ts-jest" 66 | }, 67 | "testMatch": [ 68 | "**/__tests__/*.(ts|tsx)" 69 | ] 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/__tests__/content-type.test.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from 'axios'; 2 | import debug from 'debug'; 3 | import { HTTP_HEADER } from '../enum/HTTP_HEADER'; 4 | import { HTTP_HEADER_CONTENT_TYPE } from '../enum/HTTP_HEADER_CONTENT_TYPE'; 5 | import { HTTP_METHOD } from '../enum/HTTP_METHOD'; 6 | // tslint:disable-next-line: import-name 7 | import r2curl from '../index'; 8 | 9 | // tslint:disable-next-line:no-implicit-dependencies 10 | 11 | // tslint:disable-next-line:import-name 12 | 13 | const log = debug('r2curl:tc:content-type'); 14 | 15 | describe('content-type r2curl option', () => { 16 | test('default content-type option is json utf8', () => { 17 | const config: AxiosRequestConfig = { 18 | url: 'https://google.com', 19 | method: 'POST', 20 | data: { 21 | caller: 'https://github.com/uyu423/r2curl', 22 | sorry: true, 23 | }, 24 | }; 25 | 26 | const curl = r2curl(config); 27 | 28 | log(curl); 29 | 30 | expect(curl.includes(`Content-Type:${HTTP_HEADER_CONTENT_TYPE.JSON_UTF8}`)).toBeTruthy(); 31 | }); 32 | 33 | test('"PATCH" Method has content-type option is json utf8', () => { 34 | const config: AxiosRequestConfig = { 35 | url: 'https://google.com', 36 | method: 'PATCH', 37 | data: { 38 | caller: 'https://github.com/uyu423/r2curl', 39 | sorry: true, 40 | }, 41 | }; 42 | 43 | const curl = r2curl(config); 44 | 45 | log(curl); 46 | 47 | expect(curl.includes(`Content-Type:${HTTP_HEADER_CONTENT_TYPE.JSON_UTF8}`)).toBeTruthy(); 48 | }); 49 | 50 | test('formal string default content-type', () => { 51 | const config: AxiosRequestConfig = { 52 | url: 'https://google.com', 53 | method: HTTP_METHOD.POST, 54 | }; 55 | 56 | const defaultContentType = 'application/json5'; 57 | const curl = r2curl(config, { defaultContentType }); 58 | 59 | log('json5 content-type: ', curl); 60 | 61 | expect(curl.includes(`Content-Type:${defaultContentType}`)).toBeTruthy(); 62 | }); 63 | 64 | test('defaultContentType value is "false" => not include Content-Type header', () => { 65 | const config: AxiosRequestConfig = { 66 | url: 'https://google.com', 67 | method: HTTP_METHOD.PUT, 68 | }; 69 | 70 | const defaultContentType = false; 71 | const curl = r2curl(config, { defaultContentType }); 72 | 73 | expect(curl.includes(`Content-Type:`)).toBeFalsy(); 74 | }); 75 | 76 | test('GET method not appear Content-Type Header', () => { 77 | const config: AxiosRequestConfig = { 78 | url: 'https://google.com', 79 | method: HTTP_METHOD.GET, 80 | }; 81 | 82 | const curl = r2curl(config); 83 | 84 | expect(curl.includes(`Content-Type:`)).toBeFalsy(); 85 | }); 86 | 87 | test('DELETE method not appear Content-Type Header', () => { 88 | const config: AxiosRequestConfig = { 89 | url: 'https://google.com', 90 | method: HTTP_METHOD.DELETE, 91 | }; 92 | 93 | const curl = r2curl(config); 94 | 95 | expect(curl.includes(`Content-Type:`)).toBeFalsy(); 96 | }); 97 | 98 | test('if use "forceBody" option, enable default Content-Type', () => { 99 | const config: AxiosRequestConfig = { 100 | url: 'https://google.com', 101 | method: HTTP_METHOD.GET, 102 | data: { 103 | caller: 'https://github.com/uyu423/r2curl', 104 | sorry: true, 105 | }, 106 | }; 107 | 108 | const curl = r2curl(config, { 109 | forceBody: true, 110 | }); 111 | 112 | expect(curl.includes(`Content-Type:`)).toBeTruthy(); 113 | }); 114 | }); 115 | 116 | describe('difference content-type', () => { 117 | test('difference body string as form-urlencoded content-type', done => { 118 | const config: AxiosRequestConfig = { 119 | url: 'https://google.com', 120 | method: HTTP_METHOD.POST, 121 | data: { 122 | caller: 'https://github.com/uyu423/r2curl', 123 | sorry: true, 124 | }, 125 | headers: { 126 | [HTTP_HEADER.CONTENT_TYPE]: HTTP_HEADER_CONTENT_TYPE.FORM_URLENCODED, 127 | }, 128 | }; 129 | 130 | const curl = r2curl(config); 131 | 132 | log(curl); 133 | 134 | expect(curl).toBe( 135 | // tslint:disable-next-line:max-line-length 136 | "curl -X POST 'https://google.com' -H 'Content-Type:application/x-www-form-urlencoded' --data 'caller=https%3A%2F%2Fgithub.com%2Fuyu423%2Fr2curl&sorry=true'", 137 | ); 138 | 139 | done(); 140 | }); 141 | }); 142 | -------------------------------------------------------------------------------- /src/__tests__/default.test.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import debug from 'debug'; 3 | import * as shelljs from 'shelljs'; 4 | // tslint:disable-next-line:import-name 5 | import r2curl from '../index'; 6 | 7 | const log = debug('r2curl:tc:default'); 8 | 9 | describe('default', () => { 10 | test('AxiosResponse params will success', async done => { 11 | const { devDependencies } = require('../../package.json'); 12 | const axiosVersion = (devDependencies.axios as string).replace('^', ''); 13 | 14 | const request = await axios.get('https://google.com'); 15 | const curl = r2curl(request); 16 | 17 | log(curl); 18 | 19 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 20 | 21 | expect(exec.code).toBeLessThan(1); 22 | expect(curl).toBe( 23 | // tslint:disable-next-line:max-line-length 24 | `curl -X GET 'https://google.com' -H 'Accept:application/json, text/plain, */*' -H 'User-Agent:axios/${axiosVersion}'`, 25 | ); 26 | 27 | done(); 28 | }); 29 | test('AxiosRequestConfig params will success', async done => { 30 | const config: AxiosRequestConfig = { 31 | url: 'https://google.com', 32 | method: 'POST', 33 | data: { 34 | caller: 'https://github.com/uyu423/r2curl', 35 | sorry: true, 36 | }, 37 | headers: { 38 | 'content-Type': 'application/json', 39 | }, 40 | }; 41 | 42 | const curl = r2curl(config); 43 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 44 | 45 | log(curl); 46 | 47 | expect(exec.code).toBeLessThan(1); 48 | expect(curl).toBe( 49 | // tslint:disable-next-line: max-line-length 50 | 'curl -X POST \'https://google.com\' -H \'content-Type:application/json\' --data \'{"caller":"https://github.com/uyu423/r2curl","sorry":true}\'', 51 | ); 52 | done(); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/__tests__/force-body.test.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable max-line-length 2 | import axios, { AxiosRequestConfig } from 'axios'; 3 | import debug from 'debug'; 4 | import * as shelljs from 'shelljs'; 5 | // tslint:disable-next-line:import-name 6 | import r2curl from '../index'; 7 | 8 | const log = debug('r2curl:tc:force-body-option'); 9 | 10 | describe('force-body option', () => { 11 | test('if options is false, GET not allow body', done => { 12 | const config: AxiosRequestConfig = { 13 | url: 'https://google.com', 14 | method: 'GET', 15 | data: { 16 | caller: 'https://github.com/uyu423/r2curl', 17 | sorry: true, 18 | }, 19 | }; 20 | 21 | const curl = r2curl(config, { forceBody: false }); 22 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 23 | 24 | log(curl); 25 | 26 | expect(exec.code).toBeLessThan(1); 27 | expect(curl).toBe('curl -X GET \'https://google.com\''); 28 | done(); 29 | }); 30 | 31 | test('if options is false, DELETE not allow body', done => { 32 | const config: AxiosRequestConfig = { 33 | url: 'https://google.com', 34 | method: 'DELETE', 35 | data: { 36 | caller: 'https://github.com/uyu423/r2curl', 37 | sorry: true, 38 | }, 39 | }; 40 | 41 | const curl = r2curl(config, { forceBody: false }); 42 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 43 | 44 | log(curl); 45 | 46 | expect(exec.code).toBeLessThan(1); 47 | expect(curl).toBe('curl -X DELETE \'https://google.com\''); 48 | done(); 49 | }); 50 | 51 | test('if options is true, GET allow body', done => { 52 | const config: AxiosRequestConfig = { 53 | url: 'https://google.com', 54 | method: 'GET', 55 | data: { 56 | caller: 'https://github.com/uyu423/r2curl', 57 | sorry: true, 58 | }, 59 | }; 60 | 61 | const curl = r2curl(config, { forceBody: true }); 62 | 63 | log(curl); 64 | 65 | // somtime occur http protocol error. 66 | // const exec = shelljs.exec(`${curl} --silent > /dev/null`); 67 | // expect(exec.code).toBeLessThan(1); 68 | 69 | expect(curl).toBe( 70 | 'curl -X GET \'https://google.com\' -H \'Content-Type:application/json; charset=utf-8\' --data \'{"caller":"https://github.com/uyu423/r2curl","sorry":true}\'', 71 | ); 72 | done(); 73 | }); 74 | 75 | test('if options is true, DELETE allow body', done => { 76 | const config: AxiosRequestConfig = { 77 | url: 'https://google.com', 78 | method: 'DELETE', 79 | data: { 80 | caller: 'https://github.com/uyu423/r2curl', 81 | sorry: true, 82 | }, 83 | }; 84 | 85 | const curl = r2curl(config, { forceBody: true }); 86 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 87 | 88 | log(curl); 89 | 90 | expect(exec.code).toBeLessThan(1); 91 | expect(curl).toBe( 92 | 'curl -X DELETE \'https://google.com\' -H \'Content-Type:application/json; charset=utf-8\' --data \'{"caller":"https://github.com/uyu423/r2curl","sorry":true}\'', 93 | ); 94 | done(); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /src/__tests__/option-container.test.ts: -------------------------------------------------------------------------------- 1 | import axios, { AxiosRequestConfig } from 'axios'; 2 | import debug from 'debug'; 3 | import * as shelljs from 'shelljs'; 4 | import { CURL_OPTIONS } from '../enum/CURL_OPTIONS'; 5 | // tslint:disable-next-line:import-name 6 | import r2curl from '../index'; 7 | import { defaultR2CurlOptions } from '../interface/IR2CurlOptions'; 8 | import CommonUtils from '../lib/CommonUtils'; 9 | import { OptionContainer } from '../lib/OptionContainer'; 10 | 11 | const log = debug('r2curl:tc:option-container'); 12 | 13 | describe('Option Container Class Test Case', () => { 14 | beforeAll(() => { 15 | CommonUtils.bootstrap(defaultR2CurlOptions); 16 | }); 17 | 18 | test('only add one command', done => { 19 | const oc = new OptionContainer(); 20 | oc.add(CURL_OPTIONS.COMPRESSED); 21 | expect(oc.toString()).toBe(CURL_OPTIONS.COMPRESSED); 22 | done(); 23 | }); 24 | 25 | test('only add one commmad, one value', done => { 26 | const oc = new OptionContainer(); 27 | oc.add(CURL_OPTIONS.COMPRESSED, 'Trash Value'); 28 | expect(oc.toString()).toBe(`${CURL_OPTIONS.COMPRESSED} 'Trash Value'`); 29 | done(); 30 | }); 31 | }); 32 | 33 | describe('Option Container Integration Test Case', () => { 34 | test('include header "Accept-Encoding: gzip"', done => { 35 | const config: AxiosRequestConfig = { 36 | url: 'https://google.com', 37 | method: 'POST', 38 | data: { 39 | caller: 'https://github.com/uyu423/r2curl', 40 | sorry: true, 41 | }, 42 | headers: { 43 | 'content-Type': 'application/json', 44 | 'Accept-Encoding': 'gzip', 45 | }, 46 | }; 47 | 48 | // no execute 49 | const curl = r2curl(config); 50 | const exec = shelljs.exec(`${curl} --silent > /dev/null`); 51 | 52 | log(curl); 53 | 54 | expect(exec.code).toBeLessThan(1); 55 | expect(curl).toBe( 56 | // tslint:disable-next-line: max-line-length 57 | 'curl -X POST \'https://google.com\' -H \'content-Type:application/json\' -H \'Accept-Encoding:gzip\' --data \'{"caller":"https://github.com/uyu423/r2curl","sorry":true}\' --compressed', 58 | ); 59 | done(); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/adapter/AxiosRequestConfigAdapter.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig } from 'axios'; 2 | import { HTTP_METHOD } from '../enum/HTTP_METHOD'; 3 | import IRequestAdaptor from '../interface/IRequestAdaptor'; 4 | import { isEmpty } from '../lib/isEmpty'; 5 | 6 | export class AxiosRequestConfigAdapter implements IRequestAdaptor { 7 | constructor(private readonly _prop: AxiosRequestConfig) {} 8 | 9 | get method(): HTTP_METHOD { 10 | if (isEmpty(this._prop.method)) { 11 | return HTTP_METHOD.GET; 12 | } 13 | const method = this._prop.method.toUpperCase(); 14 | switch (method) { 15 | case 'GET': { 16 | return HTTP_METHOD.GET; 17 | } 18 | case 'POST': { 19 | return HTTP_METHOD.POST; 20 | } 21 | case 'PUT': { 22 | return HTTP_METHOD.PUT; 23 | } 24 | case 'PATCH': { 25 | return HTTP_METHOD.PATCH; 26 | } 27 | case 'DELETE': { 28 | return HTTP_METHOD.DELETE; 29 | } 30 | case 'HEAD': { 31 | return HTTP_METHOD.HEAD; 32 | } 33 | case 'OPTIONS': { 34 | return HTTP_METHOD.OPTIONS; 35 | } 36 | default: { 37 | return HTTP_METHOD.GET; 38 | } 39 | } 40 | } 41 | 42 | get headers() { 43 | if (isEmpty(this._prop.headers)) { 44 | return {}; 45 | } 46 | return this._prop.headers; 47 | } 48 | 49 | get body() { 50 | return this._prop.data; 51 | } 52 | 53 | get url() { 54 | return `${this._prop.baseURL || ''}${this._prop.url || ''}`; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/enum/CURL_OPTIONS.ts: -------------------------------------------------------------------------------- 1 | export enum CURL_OPTIONS { 2 | COMPRESSED = '--compressed', 3 | } 4 | -------------------------------------------------------------------------------- /src/enum/HTTP_HEADER.ts: -------------------------------------------------------------------------------- 1 | export enum HTTP_HEADER { 2 | CONTENT_TYPE = 'Content-Type', 3 | ACCEPT_ENCODING = 'Accept-Encoding', 4 | } 5 | 6 | export enum HTTP_HEADER_LOWERCASE { 7 | CONTENT_TYPE = 'content-type', 8 | ACCEPT_ENCODING = 'accept-encoding', 9 | } 10 | -------------------------------------------------------------------------------- /src/enum/HTTP_HEADER_CONTENT_TYPE.ts: -------------------------------------------------------------------------------- 1 | export enum HTTP_HEADER_CONTENT_TYPE { 2 | TEXT = 'text/plain', 3 | JSON = 'application/json', 4 | JSON_UTF8 = 'application/json; charset=utf-8', 5 | XML = 'application/xml', 6 | HTML = 'text/html', 7 | FORM_URLENCODED = 'application/x-www-form-urlencoded', 8 | FORM_DATA = 'multipart/form-data', 9 | } 10 | -------------------------------------------------------------------------------- /src/enum/HTTP_METHOD.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Standard Http Methods 3 | * @see https://tools.ietf.org/html/rfc7231#section-4.3 4 | */ 5 | export enum HTTP_METHOD { 6 | GET = 'GET', 7 | POST = 'POST', 8 | HEAD = 'HEAD', 9 | PUT = 'PUT', 10 | DELETE = 'DELETE', 11 | OPTIONS = 'OPTIONS', 12 | CONNECT = 'CONNECT', 13 | TRACE = 'TRACE', 14 | // purge is assumed to be non-standard, but it is used in many requests. 15 | PURGE = 'PURGE', 16 | // patch is defined in https://tools.ietf.org/html/rfc5789 17 | PATCH = 'PATCH', 18 | } 19 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { AxiosRequestConfig, AxiosResponse } from 'axios'; 2 | import debug from 'debug'; 3 | import { AxiosRequestConfigAdapter } from './adapter/AxiosRequestConfigAdapter'; 4 | import { defaultR2CurlOptions, IR2CurlOptions } from './interface/IR2CurlOptions'; 5 | import IRequestAdaptor from './interface/IRequestAdaptor'; 6 | import CommonUtils from './lib/CommonUtils'; 7 | import { CurlBuilder } from './lib/CurlBuilder'; 8 | 9 | const log = debug('r2curl:index'); 10 | 11 | export default function r2curl( 12 | request: AxiosRequestConfig | AxiosResponse, 13 | option: Partial = {}, 14 | ): string { 15 | const mergedOption: IR2CurlOptions = { ...defaultR2CurlOptions, ...option }; 16 | 17 | CommonUtils.bootstrap(mergedOption); 18 | 19 | // judge request wrapper object type 20 | const adapter: IRequestAdaptor = (() => { 21 | if (((_request: any): _request is AxiosResponse => 'config' in _request)(request)) { 22 | // judge request is AxiosResponse 23 | return new AxiosRequestConfigAdapter(request.config); 24 | } 25 | // judge request is AxiosRequestConfig 26 | return new AxiosRequestConfigAdapter(request); 27 | })(); 28 | 29 | const curl = new CurlBuilder(adapter, mergedOption).toString(); 30 | log('cURL Command: ', curl); 31 | 32 | return curl; 33 | } 34 | -------------------------------------------------------------------------------- /src/interface/IR2CurlOptions.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_HEADER_CONTENT_TYPE } from '../enum/HTTP_HEADER_CONTENT_TYPE'; 2 | 3 | export interface IR2CurlOptions { 4 | /** 5 | * Determines the type of quota around the body and uri. 6 | * @default 'single' 7 | */ 8 | quote: 'single' | 'double'; 9 | /** 10 | * Determines the default Content-Type header value for POST, PATCH and PUT requests. 11 | * @default HTTP_HEADER_CONTENT_TYPE.JSON_UTF8 (appplication/json; charset=utf-8) 12 | */ 13 | defaultContentType: HTTP_HEADER_CONTENT_TYPE | string | false; 14 | /** 15 | * Accept Body all HTTP Method.(Including GET and DELETE methods) 16 | * @default false 17 | */ 18 | forceBody: boolean; 19 | } 20 | 21 | export const defaultR2CurlOptions: IR2CurlOptions = { 22 | quote: 'single', 23 | defaultContentType: HTTP_HEADER_CONTENT_TYPE.JSON_UTF8, 24 | forceBody: false, 25 | }; 26 | -------------------------------------------------------------------------------- /src/interface/IRequestAdaptor.ts: -------------------------------------------------------------------------------- 1 | import * as shelljs from 'shelljs'; 2 | import { HTTP_METHOD } from '../enum/HTTP_METHOD'; 3 | 4 | export default interface IRequestAdaptor { 5 | method: HTTP_METHOD; 6 | headers: { [key in string]: string }; 7 | body: { [key in string]: any } | Array<{ [key in string]: any }>; 8 | url: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/BodyHelper.ts: -------------------------------------------------------------------------------- 1 | import { HTTP_HEADER_LOWERCASE } from '../enum/HTTP_HEADER'; 2 | import { HTTP_HEADER_CONTENT_TYPE } from '../enum/HTTP_HEADER_CONTENT_TYPE'; 3 | import { isEmpty, isNotEmpty } from './isEmpty'; 4 | 5 | export class BodyHelper { 6 | private contentType: string | null; 7 | private body: string | null = null; 8 | 9 | constructor( 10 | private readonly _headers: { [key in string]: string }, 11 | private readonly _rawBody: { [key in string]: any } | Array<{ [key in string]: any }>, 12 | ) { 13 | this.contentType = this.getContentType(); 14 | this.body = this.parseBody(); 15 | } 16 | 17 | public toString(): string { 18 | if (isEmpty(this.body)) { 19 | return ''; 20 | } 21 | return this.body; 22 | } 23 | 24 | private getContentType(): string | null { 25 | if (isEmpty(this._headers)) { 26 | return null; 27 | } 28 | const lowerHeaderArray = Object.entries(this._headers); 29 | const [contentTypePair] = lowerHeaderArray.filter( 30 | header => header[0].toLowerCase() === HTTP_HEADER_LOWERCASE.CONTENT_TYPE, 31 | ); 32 | 33 | if (isEmpty(contentTypePair)) { 34 | return null; 35 | } 36 | return contentTypePair[1]; 37 | } 38 | 39 | private parseBody(): string | null { 40 | if (isEmpty(this._rawBody)) { 41 | return null; 42 | } 43 | if ( 44 | isNotEmpty(this.contentType) && 45 | this.contentType.includes(HTTP_HEADER_CONTENT_TYPE.FORM_URLENCODED) && 46 | isNotEmpty(this._rawBody) && 47 | typeof this._rawBody === 'object' 48 | ) { 49 | return this.getFormBody(); 50 | } 51 | return this.getTextBody(); 52 | } 53 | 54 | private getFormBody(): string { 55 | return Object.entries(this._rawBody) 56 | .map(([key, value]) => { 57 | return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; 58 | }) 59 | .join('&'); 60 | } 61 | private getTextBody(): string | null { 62 | return typeof this._rawBody === 'object' || Array.isArray(this._rawBody) ? JSON.stringify(this._rawBody) : null; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/lib/CommonUtils.ts: -------------------------------------------------------------------------------- 1 | import { IR2CurlOptions } from '../interface/IR2CurlOptions'; 2 | import { isEmpty } from './isEmpty'; 3 | 4 | export default class CommonUtils { 5 | public static bootstrap(options: IR2CurlOptions) { 6 | this.quote = options.quote === 'single' ? '\'' : '"'; 7 | } 8 | 9 | public static wrapQuote(content: string) { 10 | if (isEmpty(this.quote)) { 11 | throw new Error('CommonUtils not Bootstraped'); 12 | } 13 | return `${this.quote}${content}${this.quote}`; 14 | } 15 | 16 | private static quote: '\'' | '"'; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/CurlBuilder.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import { HTTP_METHOD } from '../enum/HTTP_METHOD'; 3 | import { IR2CurlOptions } from '../interface/IR2CurlOptions'; 4 | import IRequestAdaptor from '../interface/IRequestAdaptor'; 5 | import { BodyHelper } from './BodyHelper'; 6 | import CommonUtils from './CommonUtils'; 7 | import { HeaderHelper } from './HeaderHelper'; 8 | import { isEmpty, isNotEmpty } from './isEmpty'; 9 | import { OptionContainer } from './OptionContainer'; 10 | 11 | const log = debug('r2curl:CurlBuilder'); 12 | 13 | export class CurlBuilder { 14 | private optionContainer: OptionContainer; 15 | 16 | constructor(private readonly _adap: IRequestAdaptor, private readonly _option: IR2CurlOptions) { 17 | this.optionContainer = new OptionContainer(); 18 | } 19 | 20 | get method(): string { 21 | if (isEmpty(this._adap.method)) { 22 | return ''; 23 | } 24 | return `-X ${this._adap.method}`; 25 | } 26 | 27 | get headers(): string { 28 | const helper = new HeaderHelper(this._adap.headers, this._adap.method, this.optionContainer, this._option); 29 | const headers = helper.toObject(); 30 | if (isEmpty(headers)) { 31 | return ''; 32 | } 33 | return Object.entries(headers) 34 | .map(header => `-H ${CommonUtils.wrapQuote(`${header[0]}:${header[1]}`)}`) 35 | .join(' '); 36 | } 37 | 38 | get body(): string { 39 | log(`method: ${this._adap.method}`, `forceBody: ${this._option.forceBody}`, this._adap.body); 40 | 41 | if (!this._option.forceBody && [HTTP_METHOD.GET, HTTP_METHOD.DELETE].includes(this._adap.method)) { 42 | return ''; 43 | } 44 | 45 | const helper = new BodyHelper(this._adap.headers, this._adap.body); 46 | const body = helper.toString(); 47 | 48 | if (isEmpty(body)) { 49 | return ''; 50 | } 51 | return `--data ${CommonUtils.wrapQuote(helper.toString())}`; 52 | } 53 | 54 | get url(): string { 55 | return CommonUtils.wrapQuote(this._adap.url); 56 | } 57 | 58 | public toString() { 59 | const existData = [this.method, this.url, this.headers, this.body].filter(data => !isEmpty(data)); 60 | const curlOptions = this.optionContainer.toString(); 61 | return `curl ${[existData.join(' '), curlOptions].filter(data => isNotEmpty(data)).join(' ')}`.trim(); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/HeaderHelper.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import { CURL_OPTIONS } from '../enum/CURL_OPTIONS'; 3 | import { HTTP_HEADER, HTTP_HEADER_LOWERCASE } from '../enum/HTTP_HEADER'; 4 | import { HTTP_METHOD } from '../enum/HTTP_METHOD'; 5 | import { IR2CurlOptions } from '../interface/IR2CurlOptions'; 6 | import { isEmpty, isNotEmpty } from './isEmpty'; 7 | import { OptionContainer } from './OptionContainer'; 8 | 9 | const log = debug('r2curl:HeaderHelper'); 10 | 11 | type HttpHeaderType = { [key in string]: string }; 12 | 13 | export class HeaderHelper { 14 | private headers: { [key in string]: string } | null; 15 | // only contain lowercase key 16 | private keys: string[]; 17 | // contain lowercase key and value pair 18 | private pairs: HttpHeaderType; 19 | 20 | private defaultContentType: string | null; 21 | 22 | constructor( 23 | private readonly _rawHeaders: HttpHeaderType, 24 | private readonly _method: string, 25 | private readonly _curlOptionContainer: OptionContainer, 26 | private readonly _option: IR2CurlOptions, 27 | ) { 28 | this.defaultContentType = _option.defaultContentType === false ? null : _option.defaultContentType; 29 | 30 | this.keys = []; 31 | this.pairs = {}; 32 | 33 | Object.keys(_rawHeaders).forEach(key => { 34 | const lower = key.toLowerCase(); 35 | 36 | this.pairs[lower] = _rawHeaders[key]; 37 | this.keys.push(lower); 38 | }); 39 | 40 | this.headers = this.parseHeader(); 41 | 42 | log('keys', this.keys); 43 | } 44 | 45 | public toObject() { 46 | if (isEmpty(this.headers)) { 47 | return {}; 48 | } 49 | return this.headers; 50 | } 51 | 52 | private parseHeader() { 53 | if (isEmpty(this._rawHeaders) && isEmpty(this.defaultContentType)) { 54 | return null; 55 | } 56 | 57 | this.judgeAcceptEncoding(); 58 | 59 | return { 60 | ...this.parseContentHeader(), 61 | ...this._rawHeaders, 62 | }; 63 | } 64 | 65 | private parseContentHeader(): HttpHeaderType { 66 | log('_rawHeaders', this._rawHeaders); 67 | log('defaultContentType', this.defaultContentType); 68 | 69 | const rawHeaderContentType = this.keys.find(key => key === HTTP_HEADER_LOWERCASE.CONTENT_TYPE); 70 | const isNeedContentType = 71 | [ 72 | HTTP_METHOD.POST as string, 73 | HTTP_METHOD.PUT as string, 74 | HTTP_METHOD.PATCH as string, 75 | ].includes(this._method) || this._option.forceBody; 76 | 77 | log( 78 | 'isNeedContentType', 79 | [ 80 | HTTP_METHOD.POST as string, 81 | HTTP_METHOD.PUT as string, 82 | HTTP_METHOD.PATCH as string, 83 | ].includes(this._method), 84 | this._option.forceBody, 85 | 'OR CALC', 86 | isNeedContentType, 87 | ); 88 | 89 | const headers: HttpHeaderType = {}; 90 | 91 | if (isNeedContentType && isEmpty(rawHeaderContentType) && isNotEmpty(this.defaultContentType)) { 92 | headers[HTTP_HEADER.CONTENT_TYPE] = this.defaultContentType; 93 | } 94 | 95 | return headers; 96 | } 97 | 98 | private judgeAcceptEncoding(): void { 99 | const rawHeaderAcceptEncoding = this.keys.find(key => key === HTTP_HEADER_LOWERCASE.ACCEPT_ENCODING); 100 | 101 | log('rawHeaderAcceptEncoding:', rawHeaderAcceptEncoding); 102 | log( 103 | 'this.paris[rawHeaderAcceptEncoding]:', 104 | isNotEmpty(rawHeaderAcceptEncoding) ? this.pairs[rawHeaderAcceptEncoding] : null, 105 | ); 106 | 107 | if (isNotEmpty(rawHeaderAcceptEncoding) && this.pairs[rawHeaderAcceptEncoding] === 'gzip') { 108 | this._curlOptionContainer.add(CURL_OPTIONS.COMPRESSED); 109 | } 110 | 111 | return; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/lib/OptionContainer.ts: -------------------------------------------------------------------------------- 1 | import debug from 'debug'; 2 | import { CURL_OPTIONS } from '../enum/CURL_OPTIONS'; 3 | import CommonUtils from './CommonUtils'; 4 | import { isNotEmpty } from './isEmpty'; 5 | 6 | const log = debug('r2curl:OptionContainer'); 7 | 8 | /** curl 옵션을 구성한다. command - value 쌍이다. */ 9 | interface ICurlOption { 10 | command: string; 11 | value: string | null; 12 | } 13 | 14 | // tslint:disable-next-line: class-name 15 | export class OptionContainer { 16 | private options: ICurlOption[]; 17 | 18 | constructor() { 19 | this.options = []; 20 | } 21 | 22 | // The following methods are used from time to time when needed. 23 | public add(command: CURL_OPTIONS, value?: string) { 24 | this.options.push({ 25 | command, 26 | value: isNotEmpty(value) ? value : null, 27 | }); 28 | } 29 | 30 | public toString(): string { 31 | log(this.options); 32 | 33 | return this.options 34 | .map(option => { 35 | const value = isNotEmpty(option.value) ? CommonUtils.wrapQuote(option.value) : null; 36 | 37 | return `${option.command}${isNotEmpty(value) ? ' [[value]]'.replace('[[value]]', value) : ''}`; 38 | }) 39 | .join(' '); 40 | } 41 | 42 | // for Debug & testCase 43 | // tslint:disable-next-line: function-name 44 | public ___reset() { 45 | this.options = []; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/lib/isEmpty.ts: -------------------------------------------------------------------------------- 1 | export function isEmpty(value: T | null | undefined): value is null | undefined { 2 | if ( 3 | value === undefined || 4 | value === null || 5 | ((typeof value === 'number' && isNaN(value)) || 6 | (typeof value === 'string' && value === '') || 7 | (Array.isArray(value) && value.length < 1) || 8 | (typeof value === 'object' && Object.keys(value).length < 1)) 9 | ) { 10 | return true; 11 | } 12 | return false; 13 | } 14 | 15 | export function isNotEmpty(value: T | null | undefined): value is T { 16 | return !isEmpty(value); 17 | } 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src"], 3 | "exclude": ["node_modules", "**/__tests__/*"], 4 | "compilerOptions": { 5 | /* Basic Options */ 6 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 7 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | "outDir": "./dist", /* Redirect output structure to the directory. */ 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "incremental": true, /* Enable incremental compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | 54 | /* Source Map Options */ 55 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 56 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 57 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 58 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 59 | 60 | /* Experimental Options */ 61 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 62 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "defaultSeverity": "error", 4 | "extends": [ 5 | "tslint:latest", 6 | "tslint-config-airbnb", 7 | "tslint-config-prettier" 8 | ], 9 | "jsRules": {}, 10 | "rules": { 11 | "max-line-length": [true, 130], 12 | "variable-name": [true, "allow-leading-underscore"], 13 | "prefer-array-literal": false, 14 | "no-implicit-dependencies": [true, ["shelljs", "axios"]], 15 | "object-literal-sort-keys": false 16 | }, 17 | "rulesDirectory": [] 18 | } --------------------------------------------------------------------------------