From 6b9b05b9078c78c9532ed8c29dfb910fdc1b1018 Mon Sep 17 00:00:00 2001 From: Dmitriy Mozgovoy Date: Mon, 14 Mar 2022 20:22:10 +0200 Subject: [PATCH] Improved FormData support; (#4448) * Fixed isFormData predicate; Added support for automatic object serialization to FormData if `Content-Type` is `multipart/form-data`; Added support for FormData to be overloaded using `config.env.FormData` option; Added support for FormData in node.js environment through `form-data` package; * Added the `form-data` package as a dependency for the server build; Added tests for FormData payload; * Added FormData automatic serialization section; Refactored cancellation section; * Reworked toFormData helper; Expose toFormData helper as a static method; Refactored transform request; Added kindOf, kindOfTest, endsWith, isTypedArray util; Refactored utils.js to use kindOf for tests; * Fixed isFormData predicate; (#4413) Added support for automatic object serialization to FormData if `Content-Type` is `multipart/form-data`; Added support for FormData to be overloaded using `config.env.FormData` option; Added support for FormData in node.js environment using `form-data` package; (cherry picked from commit 73e3bdb8835ba942096b662e9441f1d85ce4d484) * Added shortcut methods `postForm`, `putForm`, `patchForm` to submit a Form; Added ability to submit FileList object as a FormData; Updated README.md; * Updated README.md; --- README.md | 129 +++++++--- index.d.ts | 6 + lib/adapters/http.js | 5 +- lib/axios.js | 1 + lib/core/Axios.js | 24 +- lib/defaults.js | 17 +- lib/defaults/env/FormData.js | 2 + lib/helpers/null.js | 2 + lib/helpers/toFormData.js | 110 ++++---- lib/utils.js | 127 +++++++-- package-lock.json | 354 ++++++++++++++++++-------- package.json | 10 +- test/specs/defaults.spec.js | 14 +- test/specs/helpers/toFormData.spec.js | 45 +++- test/specs/instance.spec.js | 4 +- test/specs/utils/endsWith.js | 10 + test/specs/utils/isX.spec.js | 5 + test/specs/utils/kindOf.js | 10 + test/specs/utils/kindOfTest.js | 10 + test/specs/utils/toArray.js | 10 + test/unit/adapters/http.js | 45 +++- test/unit/utils/isFormData.js | 12 + 22 files changed, 713 insertions(+), 239 deletions(-) create mode 100644 lib/defaults/env/FormData.js create mode 100644 lib/helpers/null.js create mode 100644 test/specs/utils/endsWith.js create mode 100644 test/specs/utils/kindOf.js create mode 100644 test/specs/utils/kindOfTest.js create mode 100644 test/specs/utils/toArray.js create mode 100644 test/unit/utils/isFormData.js diff --git a/README.md b/README.md index d5ba7f9277..0a6af128c0 100755 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Promise based HTTP client for the browser and node.js - [Example](#example) - [Axios API](#axios-api) - [Request method aliases](#request-method-aliases) - - [Concurrency (Deprecated)](#concurrency-deprecated) + - [Concurrency ๐Ÿ‘Ž](#concurrency-deprecated) - [Creating an instance](#creating-an-instance) - [Instance methods](#instance-methods) - [Request Config](#request-config) @@ -36,11 +36,15 @@ Promise based HTTP client for the browser and node.js - [Multiple Interceptors](#multiple-interceptors) - [Handling Errors](#handling-errors) - [Cancellation](#cancellation) + - [AbortController](#abortcontroller) + - [CancelToken ๐Ÿ‘Ž](#canceltoken-deprecated) - [Using application/x-www-form-urlencoded format](#using-applicationx-www-form-urlencoded-format) - [Browser](#browser) - [Node.js](#nodejs) - [Query string](#query-string) - [Form data](#form-data) + - [Automatic serialization](#-automatic-serialization) + - [Manual FormData passing](#manual-formdata-passing) - [Semver](#semver) - [Promises](#promises) - [TypeScript](#typescript) @@ -483,6 +487,11 @@ These are the available config options for making requests. Only the `url` is re // throw ETIMEDOUT error instead of generic ECONNABORTED on request timeouts clarifyTimeoutError: false, + }, + + env: { + // The FormData class to be used to automatically serialize the payload into a FormData object + FormData: window?.FormData || global?.FormData } } ``` @@ -707,10 +716,30 @@ axios.get('/user/12345') ## Cancellation -You can cancel a request using a *cancel token*. +### AbortController + +Starting from `v0.22.0` Axios supports AbortController to cancel requests in fetch API way: + +```js +const controller = new AbortController(); + +axios.get('/foo/bar', { + signal: controller.signal +}).then(function(response) { + //... +}); +// cancel the request +controller.abort() +``` + +### CancelToken `๐Ÿ‘Ždeprecated` + +You can also cancel a request using a *CancelToken*. > The axios cancel token API is based on the withdrawn [cancelable promises proposal](https://github.com/tc39/proposal-cancelable-promises). +> This API is deprecated since v0.22.0 and shouldn't be used in new projects + You can create a cancel token using the `CancelToken.source` factory as shown below: ```js @@ -754,22 +783,11 @@ axios.get('/user/12345', { cancel(); ``` -Axios supports AbortController to abort requests in [`fetch API`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#aborting_a_fetch) way: -```js -const controller = new AbortController(); - -axios.get('/foo/bar', { - signal: controller.signal -}).then(function(response) { - //... -}); -// cancel the request -controller.abort() -``` - > Note: you can cancel several requests with the same cancel token/abort controller. > If a cancellation token is already cancelled at the moment of starting an Axios request, then the request is cancelled immediately, without any attempts to make real request. +> During the transition period, you can use both cancellation APIs, even for the same request: + ## Using application/x-www-form-urlencoded format By default, axios serializes JavaScript objects to `JSON`. To send data in the `application/x-www-form-urlencoded` format instead, you can use one of the following options. @@ -829,11 +847,75 @@ axios.post('http://something.com/', params.toString()); You can also use the [`qs`](https://github.com/ljharb/qs) library. -###### NOTE -The `qs` library is preferable if you need to stringify nested objects, as the `querystring` method has known issues with that use case (https://github.com/nodejs/node-v0.x-archive/issues/1665). +> NOTE: +> The `qs` library is preferable if you need to stringify nested objects, as the `querystring` method has [known issues](https://github.com/nodejs/node-v0.x-archive/issues/1665) with that use case. #### Form data +##### ๐Ÿ†• Automatic serialization + +Starting from `v0.27.0`, Axios supports automatic object serialization to a FormData object if the request `Content-Type` +header is set to `multipart/form-data`. + +The following request will submit the data in a FormData format (Browser & Node.js): + +```js +import axios from 'axios'; + +axios.post('https://httpbin.org/post', {x: 1}, { + headers: { + 'Content-Type': 'multipart/form-data' + } +}).then(({data})=> console.log(data)); +``` + +In the `node.js` build, the ([`form-data`](https://github.com/form-data/form-data)) polyfill is used by default. + +You can overload the FormData class by setting the `env.FormData` config variable, +but you probably won't need it in most cases: + +```js +const axios= require('axios'); +var FormData = require('form-data'); + +axios.post('https://httpbin.org/post', {x: 1, buf: new Buffer(10)}, { + headers: { + 'Content-Type': 'multipart/form-data' + } +}).then(({data})=> console.log(data)); +``` + +Axios FormData serializer supports some special endings to perform the following operations: + +- `{}` - serialize the value with JSON.stringify +- `[]` - unwrap the array like object as separate fields with the same key + +```js +const axios= require('axios'); + +axios.post('https://httpbin.org/post', { + 'myObj{}': {x: 1, s: "foo"}, + 'files[]': document.querySelector('#fileInput').files +}, { + headers: { + 'Content-Type': 'multipart/form-data' + } +}).then(({data})=> console.log(data)); +``` + +Axios supports the following shortcut methods: `postForm`, `putForm`, `patchForm` +which are just the corresponding http methods with a header preset: `Content-Type`: `multipart/form-data`. + +FileList object can be passed directly: + +```js +await axios.postForm('https://httpbin.org/post', document.querySelector('#fileInput').files) +``` + +All files will be sent with the same field names: `files[]`; + +##### Manual FormData passing + In node.js, you can use the [`form-data`](https://github.com/form-data/form-data) library as follows: ```js @@ -844,18 +926,7 @@ form.append('my_field', 'my value'); form.append('my_buffer', new Buffer(10)); form.append('my_file', fs.createReadStream('/foo/bar.jpg')); -axios.post('https://example.com', form, { headers: form.getHeaders() }) -``` - -Alternatively, use an interceptor: - -```js -axios.interceptors.request.use(config => { - if (config.data instanceof FormData) { - Object.assign(config.headers, config.data.getHeaders()); - } - return config; -}); +axios.post('https://example.com', form) ``` ## Semver diff --git a/index.d.ts b/index.d.ts index b681915e16..d6ead7fd5f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -107,6 +107,9 @@ export interface AxiosRequestConfig { transitional?: TransitionalOptions; signal?: AbortSignal; insecureHTTPParser?: boolean; + env?: { + FormData?: new (...args: any[]) => object; + }; } export interface HeadersDefaults { @@ -197,6 +200,9 @@ export class Axios { post, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; put, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; patch, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; + postForm, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; + putForm, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; + patchForm, D = any>(url: string, data?: D, config?: AxiosRequestConfig): Promise; } export interface AxiosInstance extends Axios { diff --git a/lib/adapters/http.js b/lib/adapters/http.js index 951e400fa6..cc5e14a645 100755 --- a/lib/adapters/http.js +++ b/lib/adapters/http.js @@ -87,7 +87,10 @@ module.exports = function httpAdapter(config) { headers['User-Agent'] = 'axios/' + VERSION; } - if (data && !utils.isStream(data)) { + // support for https://www.npmjs.com/package/form-data api + if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) { + Object.assign(headers, data.getHeaders()); + } else if (data && !utils.isStream(data)) { if (Buffer.isBuffer(data)) { // Nothing to do... } else if (utils.isArrayBuffer(data)) { diff --git a/lib/axios.js b/lib/axios.js index c032327257..ace98bc959 100644 --- a/lib/axios.js +++ b/lib/axios.js @@ -41,6 +41,7 @@ axios.Cancel = require('./cancel/Cancel'); axios.CancelToken = require('./cancel/CancelToken'); axios.isCancel = require('./cancel/isCancel'); axios.VERSION = require('./env/data').version; +axios.toFormData = require('./helpers/toFormData'); // Expose all/spread axios.all = function all(promises) { diff --git a/lib/core/Axios.js b/lib/core/Axios.js index a1be08adae..cc820a618d 100644 --- a/lib/core/Axios.js +++ b/lib/core/Axios.js @@ -136,13 +136,23 @@ utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { /*eslint func-names:0*/ - Axios.prototype[method] = function(url, data, config) { - return this.request(mergeConfig(config || {}, { - method: method, - url: url, - data: data - })); - }; + + function generateHTTPMethod(isForm) { + return function httpMethod(url, data, config) { + return this.request(mergeConfig(config || {}, { + method: method, + headers: isForm ? { + 'Content-Type': 'multipart/form-data' + } : {}, + url: url, + data: data + })); + }; + } + + Axios.prototype[method] = generateHTTPMethod(); + + Axios.prototype[method + 'Form'] = generateHTTPMethod(true); }); module.exports = Axios; diff --git a/lib/defaults.js b/lib/defaults.js index eaee1898a0..755e09d9b6 100644 --- a/lib/defaults.js +++ b/lib/defaults.js @@ -3,6 +3,7 @@ var utils = require('./utils'); var normalizeHeaderName = require('./helpers/normalizeHeaderName'); var enhanceError = require('./core/enhanceError'); +var toFormData = require('./helpers/toFormData'); var DEFAULT_CONTENT_TYPE = { 'Content-Type': 'application/x-www-form-urlencoded' @@ -71,10 +72,20 @@ var defaults = { setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); return data.toString(); } - if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) { + + var isObjectPayload = utils.isObject(data); + var contentType = headers && headers['Content-Type']; + + var isFileList; + + if ((isFileList = utils.isFileList(data)) || (isObjectPayload && contentType === 'multipart/form-data')) { + var _FormData = this.env && this.env.FormData; + return toFormData(isFileList ? {'files[]': data} : data, _FormData && new _FormData()); + } else if (isObjectPayload || contentType === 'application/json') { setContentTypeIfUnset(headers, 'application/json'); return stringifySafely(data); } + return data; }], @@ -112,6 +123,10 @@ var defaults = { maxContentLength: -1, maxBodyLength: -1, + env: { + FormData: require('./defaults/env/FormData') + }, + validateStatus: function validateStatus(status) { return status >= 200 && status < 300; }, diff --git a/lib/defaults/env/FormData.js b/lib/defaults/env/FormData.js new file mode 100644 index 0000000000..a8e6bdf264 --- /dev/null +++ b/lib/defaults/env/FormData.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line strict +module.exports = require('form-data'); diff --git a/lib/helpers/null.js b/lib/helpers/null.js new file mode 100644 index 0000000000..859baceb50 --- /dev/null +++ b/lib/helpers/null.js @@ -0,0 +1,2 @@ +// eslint-disable-next-line strict +module.exports = null; diff --git a/lib/helpers/toFormData.js b/lib/helpers/toFormData.js index e21d0a7fa6..4e043fab93 100644 --- a/lib/helpers/toFormData.js +++ b/lib/helpers/toFormData.js @@ -1,55 +1,73 @@ 'use strict'; -function combinedKey(parentKey, elKey) { - return parentKey + '.' + elKey; -} +var utils = require('../utils'); + +/** + * Convert a data object to FormData + * @param {Object} obj + * @param {?Object} [formData] + * @returns {Object} + **/ + +function toFormData(obj, formData) { + // eslint-disable-next-line no-param-reassign + formData = formData || new FormData(); + + var stack = []; + + function convertValue(value) { + if (value === null) return ''; -function buildFormData(formData, data, parentKey) { - if (Array.isArray(data)) { - data.forEach(function buildArray(el, i) { - buildFormData(formData, el, combinedKey(parentKey, i)); - }); - } else if ( - typeof data === 'object' && - !(data instanceof File || data === null) - ) { - Object.keys(data).forEach(function buildObject(key) { - buildFormData( - formData, - data[key], - parentKey ? combinedKey(parentKey, key) : key - ); - }); - } else { - if (data === undefined) { - return; + if (utils.isDate(value)) { + return value.toISOString(); } - var value = - typeof data === 'boolean' || typeof data === 'number' - ? data.toString() - : data; - formData.append(parentKey, value); + if (utils.isArrayBuffer(value) || utils.isTypedArray(value)) { + return typeof Blob === 'function' ? new Blob([value]) : Buffer.from(value); + } + + return value; } -} -/** - * convert a data object to FormData - * - * type FormDataPrimitive = string | Blob | number | boolean - * interface FormDataNest { - * [x: string]: FormVal - * } - * - * type FormVal = FormDataNest | FormDataPrimitive - * - * @param {FormVal} data - */ - -module.exports = function getFormData(data) { - var formData = new FormData(); - - buildFormData(formData, data); + function build(data, parentKey) { + if (utils.isPlainObject(data) || utils.isArray(data)) { + if (stack.indexOf(data) !== -1) { + throw Error('Circular reference detected in ' + parentKey); + } + + stack.push(data); + + utils.forEach(data, function each(value, key) { + if (utils.isUndefined(value)) return; + + var fullKey = parentKey ? parentKey + '.' + key : key; + var arr; + + if (value && !parentKey && typeof value === 'object') { + if (utils.endsWith(key, '{}')) { + // eslint-disable-next-line no-param-reassign + value = JSON.stringify(value); + } else if (utils.endsWith(key, '[]') && (arr = utils.toArray(value))) { + // eslint-disable-next-line func-names + arr.forEach(function(el) { + !utils.isUndefined(el) && formData.append(fullKey, convertValue(el)); + }); + return; + } + } + + build(value, fullKey); + }); + + stack.pop(); + } else { + formData.append(parentKey, convertValue(data)); + } + } + + build(obj); return formData; -}; +} + +module.exports = toFormData; diff --git a/lib/utils.js b/lib/utils.js index f0f90432c5..7fe29979fa 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -6,6 +6,22 @@ var bind = require('./helpers/bind'); var toString = Object.prototype.toString; +// eslint-disable-next-line func-names +var kindOf = (function(cache) { + // eslint-disable-next-line func-names + return function(thing) { + var str = toString.call(thing); + return cache[str] || (cache[str] = str.slice(8, -1).toLowerCase()); + }; +})(Object.create(null)); + +function kindOfTest(type) { + type = type.toLowerCase(); + return function isKindOf(thing) { + return kindOf(thing) === type; + }; +} + /** * Determine if a value is an Array * @@ -40,22 +56,12 @@ function isBuffer(val) { /** * Determine if a value is an ArrayBuffer * + * @function * @param {Object} val The value to test * @returns {boolean} True if value is an ArrayBuffer, otherwise false */ -function isArrayBuffer(val) { - return toString.call(val) === '[object ArrayBuffer]'; -} +var isArrayBuffer = kindOfTest('ArrayBuffer'); -/** - * Determine if a value is a FormData - * - * @param {Object} val The value to test - * @returns {boolean} True if value is an FormData, otherwise false - */ -function isFormData(val) { - return toString.call(val) === '[object FormData]'; -} /** * Determine if a value is a view on an ArrayBuffer @@ -110,7 +116,7 @@ function isObject(val) { * @return {boolean} True if value is a plain Object, otherwise false */ function isPlainObject(val) { - if (toString.call(val) !== '[object Object]') { + if (kindOf(val) !== 'object') { return false; } @@ -121,32 +127,38 @@ function isPlainObject(val) { /** * Determine if a value is a Date * + * @function * @param {Object} val The value to test * @returns {boolean} True if value is a Date, otherwise false */ -function isDate(val) { - return toString.call(val) === '[object Date]'; -} +var isDate = kindOfTest('Date'); /** * Determine if a value is a File * + * @function * @param {Object} val The value to test * @returns {boolean} True if value is a File, otherwise false */ -function isFile(val) { - return toString.call(val) === '[object File]'; -} +var isFile = kindOfTest('File'); /** * Determine if a value is a Blob * + * @function * @param {Object} val The value to test * @returns {boolean} True if value is a Blob, otherwise false */ -function isBlob(val) { - return toString.call(val) === '[object Blob]'; -} +var isBlob = kindOfTest('Blob'); + +/** + * Determine if a value is a FileList + * + * @function + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ +var isFileList = kindOfTest('FileList'); /** * Determine if a value is a Function @@ -169,14 +181,27 @@ function isStream(val) { } /** - * Determine if a value is a URLSearchParams object + * Determine if a value is a FormData * + * @param {Object} thing The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ +function isFormData(thing) { + var pattern = '[object FormData]'; + return thing && ( + (typeof FormData === 'function' && thing instanceof FormData) || + toString.call(thing) === pattern || + (isFunction(thing.toString) && thing.toString() === pattern) + ); +} + +/** + * Determine if a value is a URLSearchParams object + * @function * @param {Object} val The value to test * @returns {boolean} True if value is a URLSearchParams object, otherwise false */ -function isURLSearchParams(val) { - return toString.call(val) === '[object URLSearchParams]'; -} +var isURLSearchParams = kindOfTest('URLSearchParams'); /** * Trim excess whitespace off the beginning and end of a string @@ -323,6 +348,48 @@ function stripBOM(content) { return content; } +/** + * determines whether a string ends with the characters of a specified string + * @param {String} str + * @param {String} searchString + * @param {Number} [position= 0] + * @returns {boolean} + */ +function endsWith(str, searchString, position) { + str = String(str); + if (position === undefined || position > str.length) { + position = str.length; + } + position -= searchString.length; + var lastIndex = str.indexOf(searchString, position); + return lastIndex !== -1 && lastIndex === position; +} + + +/** + * Returns new array from array like object + * @param {*} [thing] + * @returns {Array} + */ +function toArray(thing) { + if (!thing) return null; + var i = thing.length; + if (isUndefined(i)) return null; + var arr = new Array(i); + while (i-- > 0) { + arr[i] = thing[i]; + } + return arr; +} + +// eslint-disable-next-line func-names +var isTypedArray = (function(TypedArray) { + // eslint-disable-next-line func-names + return function(thing) { + return TypedArray && thing instanceof TypedArray; + }; +})(typeof Uint8Array !== 'undefined' && Object.getPrototypeOf(Uint8Array)); + module.exports = { isArray: isArray, isArrayBuffer: isArrayBuffer, @@ -345,5 +412,11 @@ module.exports = { merge: merge, extend: extend, trim: trim, - stripBOM: stripBOM + stripBOM: stripBOM, + kindOf: kindOf, + kindOfTest: kindOfTest, + endsWith: endsWith, + toArray: toArray, + isTypedArray: isTypedArray, + isFileList: isFileList }; diff --git a/package-lock.json b/package-lock.json index a7391e812f..57a5cb028e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,16 +5,20 @@ "requires": true, "packages": { "": { + "name": "axios", "version": "0.26.0", "license": "MIT", "dependencies": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.14.8", + "form-data": "^4.0.0" }, "devDependencies": { "abortcontroller-polyfill": "^1.5.0", "coveralls": "^3.0.0", + "cross-env": "^7.0.3", "dtslint": "^4.1.6", "es6-promise": "^4.2.4", + "formidable": "^2.0.1", "grunt": "^1.3.0", "grunt-banner": "^0.6.0", "grunt-cli": "^1.2.0", @@ -1265,6 +1269,12 @@ "node": ">=0.10.0" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -1365,8 +1375,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -2679,7 +2688,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -3096,6 +3104,24 @@ "sha.js": "^2.4.8" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -3169,9 +3195,9 @@ } }, "node_modules/date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", "dev": true, "engines": { "node": ">=4.0" @@ -3685,7 +3711,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -3784,6 +3809,16 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -5652,17 +5687,43 @@ } }, "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dependencies": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" }, "engines": { - "node": ">= 0.12" + "node": ">= 6" + } + }, + "node_modules/formidable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", + "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "dev": true, + "dependencies": { + "dezalgo": "1.0.3", + "hexoid": "1.0.0", + "once": "1.4.0", + "qs": "6.9.3" + }, + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/formidable/node_modules/qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "dev": true, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/forwarded": { @@ -6835,6 +6896,15 @@ "tslib": "^2.0.3" } }, + "node_modules/hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7949,9 +8019,9 @@ "dev": true }, "node_modules/karma": { - "version": "6.3.14", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.14.tgz", - "integrity": "sha512-SDFoU5F4LdosEiUVWUDRPCV/C1zQRNtIakx7rWkigf7R4sxGADlSEeOma4S1f/js7YAzvqLW92ByoiQptg+8oQ==", + "version": "6.3.11", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.11.tgz", + "integrity": "sha512-QGUh4yXgizzDNPLB5nWTvP+wysKexngbyLVWFOyikB661hpa2RZLf5anZQzqliWtAQuYVep0ot0D1U7UQKpsxQ==", "dev": true, "dependencies": { "body-parser": "^1.19.0", @@ -7966,7 +8036,7 @@ "http-proxy": "^1.18.1", "isbinaryfile": "^4.0.8", "lodash": "^4.17.21", - "log4js": "^6.4.1", + "log4js": "^6.3.0", "mime": "^2.5.2", "minimatch": "^3.0.4", "qjobs": "^1.2.0", @@ -8699,21 +8769,27 @@ } }, "node_modules/log4js": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", - "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", "dev": true, "dependencies": { - "date-format": "^4.0.3", - "debug": "^4.3.3", - "flatted": "^3.2.4", - "rfdc": "^1.3.0", - "streamroller": "^3.0.2" + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" }, "engines": { "node": ">=8.0" } }, + "node_modules/log4js/node_modules/flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + }, "node_modules/loglevel": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.0.tgz", @@ -9117,7 +9193,6 @@ "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -9126,7 +9201,6 @@ "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "dependencies": { "mime-db": "1.51.0" }, @@ -11435,6 +11509,20 @@ "node": ">= 6" } }, + "node_modules/request/node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/request/node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -13089,52 +13177,40 @@ "dev": true }, "node_modules/streamroller": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", - "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", "dev": true, "dependencies": { - "date-format": "^4.0.3", + "date-format": "^2.1.0", "debug": "^4.1.1", - "fs-extra": "^10.0.0" + "fs-extra": "^8.1.0" }, "engines": { "node": ">=8.0" } }, - "node_modules/streamroller/node_modules/fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "node_modules/streamroller/node_modules/date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", "dev": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, "engines": { - "node": ">=12" + "node": ">=4.0" } }, - "node_modules/streamroller/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/streamroller/node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "dependencies": { - "universalify": "^2.0.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/streamroller/node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { - "node": ">= 10.0.0" + "node": ">=6 <7 || >=8" } }, "node_modules/strict-uri-encode": { @@ -17200,6 +17276,12 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true + }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -17295,8 +17377,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "at-least-node": { "version": "1.0.0", @@ -18407,7 +18488,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -18775,6 +18855,15 @@ "sha.js": "^2.4.8" } }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -18839,9 +18928,9 @@ } }, "date-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.3.tgz", - "integrity": "sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-3.0.0.tgz", + "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==", "dev": true }, "dateformat": { @@ -19248,8 +19337,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegates": { "version": "1.0.0", @@ -19332,6 +19420,16 @@ "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==", "dev": true }, + "dezalgo": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", + "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "dev": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -20873,16 +20971,35 @@ "dev": true }, "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, + "formidable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz", + "integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==", + "dev": true, + "requires": { + "dezalgo": "1.0.3", + "hexoid": "1.0.0", + "once": "1.4.0", + "qs": "6.9.3" + }, + "dependencies": { + "qs": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz", + "integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==", + "dev": true + } + } + }, "forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -21802,6 +21919,12 @@ "tslib": "^2.0.3" } }, + "hexoid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz", + "integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -22675,9 +22798,9 @@ "dev": true }, "karma": { - "version": "6.3.14", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.14.tgz", - "integrity": "sha512-SDFoU5F4LdosEiUVWUDRPCV/C1zQRNtIakx7rWkigf7R4sxGADlSEeOma4S1f/js7YAzvqLW92ByoiQptg+8oQ==", + "version": "6.3.11", + "resolved": "https://registry.npmjs.org/karma/-/karma-6.3.11.tgz", + "integrity": "sha512-QGUh4yXgizzDNPLB5nWTvP+wysKexngbyLVWFOyikB661hpa2RZLf5anZQzqliWtAQuYVep0ot0D1U7UQKpsxQ==", "dev": true, "requires": { "body-parser": "^1.19.0", @@ -22692,7 +22815,7 @@ "http-proxy": "^1.18.1", "isbinaryfile": "^4.0.8", "lodash": "^4.17.21", - "log4js": "^6.4.1", + "log4js": "^6.3.0", "mime": "^2.5.2", "minimatch": "^3.0.4", "qjobs": "^1.2.0", @@ -23287,16 +23410,24 @@ } }, "log4js": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.1.tgz", - "integrity": "sha512-iUiYnXqAmNKiIZ1XSAitQ4TmNs8CdZYTAWINARF3LjnsLN8tY5m0vRwd6uuWj/yNY0YHxeZodnbmxKFUOM2rMg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.3.0.tgz", + "integrity": "sha512-Mc8jNuSFImQUIateBFwdOQcmC6Q5maU0VVvdC2R6XMb66/VnT+7WS4D/0EeNMZu1YODmJe5NIn2XftCzEocUgw==", "dev": true, "requires": { - "date-format": "^4.0.3", - "debug": "^4.3.3", - "flatted": "^3.2.4", - "rfdc": "^1.3.0", - "streamroller": "^3.0.2" + "date-format": "^3.0.0", + "debug": "^4.1.1", + "flatted": "^2.0.1", + "rfdc": "^1.1.4", + "streamroller": "^2.2.4" + }, + "dependencies": { + "flatted": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", + "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", + "dev": true + } } }, "loglevel": { @@ -23635,14 +23766,12 @@ "mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", - "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==" }, "mime-types": { "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", - "dev": true, "requires": { "mime-db": "1.51.0" } @@ -25492,6 +25621,17 @@ "uuid": "^3.3.2" }, "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -26881,42 +27021,32 @@ "dev": true }, "streamroller": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.2.tgz", - "integrity": "sha512-ur6y5S5dopOaRXBuRIZ1u6GC5bcEXHRZKgfBjfCglMhmIf+roVCECjvkEYzNQOXIN2/JPnkMPW/8B3CZoKaEPA==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-2.2.4.tgz", + "integrity": "sha512-OG79qm3AujAM9ImoqgWEY1xG4HX+Lw+yY6qZj9R1K2mhF5bEmQ849wvrb+4vt4jLMLzwXttJlQbOdPOQVRv7DQ==", "dev": true, "requires": { - "date-format": "^4.0.3", + "date-format": "^2.1.0", "debug": "^4.1.1", - "fs-extra": "^10.0.0" + "fs-extra": "^8.1.0" }, "dependencies": { + "date-format": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz", + "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==", + "dev": true + }, "fs-extra": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, "requires": { "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } - }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" } - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true } } }, diff --git a/package.json b/package.json index afedad7ac3..df66a67bf5 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "test": "grunt test && dtslint", "start": "node ./sandbox/server.js", - "build": "NODE_ENV=production grunt build", + "build": "cross-env NODE_ENV=production grunt build", "preversion": "grunt version && npm test", "version": "npm run build && git add -A dist && git add CHANGELOG.md bower.json package.json", "postversion": "git push && git push --tags", @@ -35,8 +35,10 @@ "devDependencies": { "abortcontroller-polyfill": "^1.5.0", "coveralls": "^3.0.0", + "cross-env": "^7.0.3", "dtslint": "^4.1.6", "es6-promise": "^4.2.4", + "formidable": "^2.0.1", "grunt": "^1.3.0", "grunt-banner": "^0.6.0", "grunt-cli": "^1.2.0", @@ -69,13 +71,15 @@ "webpack-dev-server": "^3.11.0" }, "browser": { - "./lib/adapters/http.js": "./lib/adapters/xhr.js" + "./lib/adapters/http.js": "./lib/adapters/xhr.js", + "./lib/defaults/env/FormData.js": "./lib/helpers/null.js" }, "jsdelivr": "dist/axios.min.js", "unpkg": "dist/axios.min.js", "typings": "./index.d.ts", "dependencies": { - "follow-redirects": "^1.14.8" + "follow-redirects": "^1.14.8", + "form-data": "^4.0.0" }, "bundlesize": [ { diff --git a/test/specs/defaults.spec.js b/test/specs/defaults.spec.js index ff0f15febe..58fc74937f 100644 --- a/test/specs/defaults.spec.js +++ b/test/specs/defaults.spec.js @@ -31,7 +31,19 @@ describe('defaults', function () { expect(defaults.transformRequest[0](true, headers)).toEqual('true'); expect(defaults.transformRequest[0](false, headers)).toEqual('false'); expect(defaults.transformRequest[0](null, headers)).toEqual('null'); - }); + }); + + it("should transform the plain data object to a FormData instance 'Content-Type' if header is 'multipart/form-data'", function() { + var headers = { + 'Content-Type': 'multipart/form-data' + }; + + var payload = {x: 1}; + + var transformed = defaults.transformRequest[0](payload, headers); + + expect(transformed).toEqual(jasmine.any(FormData)); + }); it('should do nothing to request string', function () { expect(defaults.transformRequest[0]('foo=bar')).toEqual('foo=bar'); diff --git a/test/specs/helpers/toFormData.spec.js b/test/specs/helpers/toFormData.spec.js index f28471ece3..d833f2e4eb 100644 --- a/test/specs/helpers/toFormData.spec.js +++ b/test/specs/helpers/toFormData.spec.js @@ -1,18 +1,43 @@ -var toFormData = require("../../../lib/helpers/toFormData"); +var toFormData = require('../../../lib/helpers/toFormData'); -describe("toFormData", function () { - it("Convert nested data object to FormDAta", function () { +describe('toFormData', function () { + it('should convert nested data object to FormData', function () { var o = { val: 123, nested: { - arr: ["hello", "world"], - }, + arr: ['hello', 'world'] + } }; - convertedVal = toFormData(o); - expect(convertedVal instanceof FormData).toEqual(true); - expect(Array.from(convertedVal.keys()).length).toEqual(3); - expect(convertedVal.get("val")).toEqual("123") - expect(convertedVal.get("nested.arr.0")).toEqual("hello") + var form = toFormData(o); + expect(form instanceof FormData).toEqual(true); + expect(Array.from(form.keys()).length).toEqual(3); + expect(form.get('val')).toEqual('123'); + expect(form.get('nested.arr.0')).toEqual('hello'); + }); + + it('should append value whose key ends with [] as separate values with the same key', function () { + var data = { + 'arr[]': [1, 2, 3] + }; + + var form = toFormData(data); + + expect(Array.from(form.keys()).length).toEqual(3); + expect(form.getAll('arr[]')).toEqual(['1', '2', '3']); + }); + + it('should append value whose key ends with {} as a JSON string', function () { + var data = { + 'obj{}': {x: 1, y: 2} + }; + + var str = JSON.stringify(data['obj{}']); + + var form = toFormData(data); + + expect(Array.from(form.keys()).length).toEqual(1); + expect(form.getAll('obj{}')).toEqual([str]); }); }); + diff --git a/test/specs/instance.spec.js b/test/specs/instance.spec.js index f424892053..6c2e438ae7 100644 --- a/test/specs/instance.spec.js +++ b/test/specs/instance.spec.js @@ -21,7 +21,9 @@ describe('instance', function () { 'spread', 'isAxiosError', 'VERSION', - 'default'].indexOf(prop) > -1) { + 'default', + 'toFormData' + ].indexOf(prop) > -1) { continue; } expect(typeof instance[prop]).toBe(typeof axios[prop]); diff --git a/test/specs/utils/endsWith.js b/test/specs/utils/endsWith.js new file mode 100644 index 0000000000..e541d4add7 --- /dev/null +++ b/test/specs/utils/endsWith.js @@ -0,0 +1,10 @@ +var kindOf = require('../../../lib/utils').kindOf; + +describe('utils::kindOf', function () { + it('should return object tag', function () { + expect(kindOf({})).toEqual('object'); + // cached result + expect(kindOf({})).toEqual('object'); + expect(kindOf([])).toEqual('array'); + }); +}); diff --git a/test/specs/utils/isX.spec.js b/test/specs/utils/isX.spec.js index 59b3168c43..f45fb71ed9 100644 --- a/test/specs/utils/isX.spec.js +++ b/test/specs/utils/isX.spec.js @@ -77,4 +77,9 @@ describe('utils::isX', function () { expect(utils.isURLSearchParams(new URLSearchParams())).toEqual(true); expect(utils.isURLSearchParams('foo=1&bar=2')).toEqual(false); }); + + it('should validate TypedArray instance', function () { + expect(utils.isTypedArray(new Uint8Array([1, 2, 3]))).toEqual(true); + expect(utils.isTypedArray([1, 2, 3])).toEqual(false); + }); }); diff --git a/test/specs/utils/kindOf.js b/test/specs/utils/kindOf.js new file mode 100644 index 0000000000..fdf28e57be --- /dev/null +++ b/test/specs/utils/kindOf.js @@ -0,0 +1,10 @@ +var kindOfTest = require('../../../lib/utils').kindOfTest; + +describe('utils::endsWith', function () { + it('should return true if the string ends with passed substring', function () { + var test = kindOfTest('number'); + + expect(test(123)).toEqual(true); + expect(test('123')).toEqual(false); + }); +}); diff --git a/test/specs/utils/kindOfTest.js b/test/specs/utils/kindOfTest.js new file mode 100644 index 0000000000..e541d4add7 --- /dev/null +++ b/test/specs/utils/kindOfTest.js @@ -0,0 +1,10 @@ +var kindOf = require('../../../lib/utils').kindOf; + +describe('utils::kindOf', function () { + it('should return object tag', function () { + expect(kindOf({})).toEqual('object'); + // cached result + expect(kindOf({})).toEqual('object'); + expect(kindOf([])).toEqual('array'); + }); +}); diff --git a/test/specs/utils/toArray.js b/test/specs/utils/toArray.js new file mode 100644 index 0000000000..6461da0645 --- /dev/null +++ b/test/specs/utils/toArray.js @@ -0,0 +1,10 @@ +var toArray = require('../../../lib/utils').toArray; + +describe('utils::kindOf', function () { + it('should return object tag', function () { + expect(toArray()).toEqual(null); + expect(toArray([])).toEqual([]); + expect(toArray([1])).toEqual([1]); + expect(toArray([1, 2, 3])).toEqual([1, 2, 3]); + }); +}); diff --git a/test/unit/adapters/http.js b/test/unit/adapters/http.js index 018c2fc6cc..6aba51d1c8 100644 --- a/test/unit/adapters/http.js +++ b/test/unit/adapters/http.js @@ -9,6 +9,8 @@ var fs = require('fs'); var path = require('path'); var pkg = require('./../../../package.json'); var server, proxy; +var FormData = require('form-data'); +var formidable = require('formidable'); describe('supports http with nodejs', function () { @@ -494,7 +496,7 @@ describe('supports http with nodejs', function () { it('should display error while parsing params', function (done) { server = http.createServer(function () { - + }).listen(4444, function () { axios.get('http://localhost:4444/', { params: { @@ -1078,5 +1080,46 @@ describe('supports http with nodejs', function () { }); }); + it('should allow passing FormData', function (done) { + var form = new FormData(); + var file1= Buffer.from('foo', 'utf8'); + + form.append('foo', "bar"); + form.append('file1', file1, { + filename: 'bar.jpg', + filepath: 'temp/bar.jpg', + contentType: 'image/jpeg' + }); + + server = http.createServer(function (req, res) { + var receivedForm = new formidable.IncomingForm(); + + receivedForm.parse(req, function (err, fields, files) { + if (err) { + return done(err); + } + + res.end(JSON.stringify({ + fields: fields, + files: files + })); + }); + }).listen(4444, function () { + axios.post('http://localhost:4444/', form, { + headers: { + 'Content-Type': 'multipart/form-data' + } + }).then(function (res) { + assert.deepStrictEqual(res.data.fields,{foo: 'bar'}); + + assert.strictEqual(res.data.files.file1.mimetype,'image/jpeg'); + assert.strictEqual(res.data.files.file1.originalFilename,'temp/bar.jpg'); + assert.strictEqual(res.data.files.file1.size,3); + + done(); + }).catch(done); + }); + }); + }); diff --git a/test/unit/utils/isFormData.js b/test/unit/utils/isFormData.js new file mode 100644 index 0000000000..91a8ec10c4 --- /dev/null +++ b/test/unit/utils/isFormData.js @@ -0,0 +1,12 @@ +var assert = require('assert'); +var isFormData = require('../../../lib/utils').isFormData; +var FormData = require('form-data'); + +describe('utils::isFormData', function () { + it('should detect the FormData instance provided by the `form-data` package', function () { + [1, 'str', {}, new RegExp()].forEach(function (thing) { + assert.equal(isFormData(thing), false); + }); + assert.equal(isFormData(new FormData()), true); + }); +});