mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-25 04:34:06 -08:00
feat(HTTP Request Node): Redesign and add the ability to import cURL commands (#3860)
* ⚡ Initial commit * 👕 Fix linting issue * ⚡ Add import button * ⚡ Remove ligh versioning * ⚡ Improvements * ⚡ Improvements * 🔥 Remove HttpRequest2 file used for testing * 🐛 Fix building issue * ⚡ Small improvement * 👕 Fix linting issue * 🔥 Remove HttpRequest2 from loader * ⚡ Update package-lock.json * ⚡ Improvements * ⚡ Small change * 🐛 Fix issue retrieving splitIntoItems * 🐛 Fix issue retrieving neverError parameter * 🐛 Fix issue with displayOptions * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Improvements * ⚡ Move cURL section to its own component * ⚡ Improvements * ⚡ Improvements * ⚡ Add fix for batching in all versions * ⚡ Add notice to cURL modal * 🔥 Remove comments * ⚡ Improvements * ⚡ Type curl-to-json endpoint * ⚡ Fix typo * 🔥 Remove console.logs * ⚡ Fix typo in curl-to-json endpoint * ⚡ Improvements * ⚡ Improvements * ⚡ Update package-lock.json * ⚡ Rename import modal constant * ⚡ Add return types to methods * ⚡ Add CSS modules to ImportParameter component * ⚡ Rename ImportParameter component to use kebab-case * ⚡ Improvements * ⚡ update package-lock.json * ⚡ Fix linting issues * Fix issue with css reference in ImportParameter component * ⚡ Small improvements * ⚡ Rename redirects to redirect * ⚡ Allow to set multiple parameters on valueChanged * 👕 Fix linting issue * 🐛 Add mistakenly removed openExistingCredentials * ⚡ Improve curl regex * ⚡ Keep headers as defined in the cURL command * ⚡ Account for all protocols supported by cURL * ⚡ Add tests * 🔥 Remove unnecessary lines * ⚡ Add more testing * ⚡ Add noDataExpression to dependent fields * 🐛 Fix bug not handling multipart-form data correctly * ⚡ Change error messages * 🐛 Fix response format string for empty values * Fix typo
This commit is contained in:
parent
5526057efc
commit
f37d6ba03b
297
package-lock.json
generated
297
package-lock.json
generated
|
@ -2485,6 +2485,57 @@
|
||||||
"node": ">=0.1.90"
|
"node": ">=0.1.90"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@curlconverter/yargs": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@curlconverter/yargs-parser": "^0.0.1",
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs-parser": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||||
|
"engines": {
|
||||||
|
"node": "6.* || 8.* || >= 10.*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@curlconverter/yargs/node_modules/y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@dabh/diagnostics": {
|
"node_modules/@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
|
@ -9038,6 +9089,11 @@
|
||||||
"vite": "^2.1.5 || ^3.0.5"
|
"vite": "^2.1.5 || ^3.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/a-sync-waterfall": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
|
},
|
||||||
"node_modules/abab": {
|
"node_modules/abab": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||||
|
@ -9808,8 +9864,7 @@
|
||||||
"node_modules/asap": {
|
"node_modules/asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/asn1": {
|
"node_modules/asn1": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
|
@ -13788,6 +13843,59 @@
|
||||||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/curlconverter": {
|
||||||
|
"version": "3.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz",
|
||||||
|
"integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==",
|
||||||
|
"dependencies": {
|
||||||
|
"@curlconverter/yargs": "^0.0.2",
|
||||||
|
"cookie": "^0.4.1",
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
|
"nunjucks": "^3.2.3",
|
||||||
|
"query-string": "^7.0.1",
|
||||||
|
"string.prototype.startswith": "^1.0.0",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"curlconverter": "bin/cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/jsesc": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
|
||||||
|
"bin": {
|
||||||
|
"jsesc": "bin/jsesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/query-string": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||||
|
"dependencies": {
|
||||||
|
"decode-uri-component": "^0.2.0",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/curlconverter/node_modules/strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/currently-unhandled": {
|
"node_modules/currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -13958,7 +14066,6 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10"
|
"node": ">=0.10"
|
||||||
}
|
}
|
||||||
|
@ -17037,6 +17144,14 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/finalhandler": {
|
"node_modules/finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
@ -28003,6 +28118,38 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nunjucks": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"a-sync-waterfall": "^1.0.0",
|
||||||
|
"asap": "^2.0.3",
|
||||||
|
"commander": "^5.1.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nunjucks-precompile": "bin/precompile"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6.9.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"chokidar": "^3.3.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"chokidar": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nunjucks/node_modules/commander": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nwsapi": {
|
"node_modules/nwsapi": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
||||||
|
@ -33584,6 +33731,14 @@
|
||||||
"node": ">=4.5"
|
"node": ">=4.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/split-string": {
|
"node_modules/split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
|
@ -34265,6 +34420,15 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/string.prototype.startswith": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==",
|
||||||
|
"dependencies": {
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string.prototype.trimend": {
|
"node_modules/string.prototype.trimend": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
|
@ -41556,6 +41720,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
@ -44294,6 +44459,47 @@
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="
|
||||||
},
|
},
|
||||||
|
"@curlconverter/yargs": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs/-/yargs-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-Q1YEebpCY61kxme4wvU0/IN/uMBfG5pZOKCo9FU+w20ElPvN+eH2qEVbK1C12t3Tee3qeYLLEU6HkiUeO1gc4A==",
|
||||||
|
"requires": {
|
||||||
|
"@curlconverter/yargs-parser": "^0.0.1",
|
||||||
|
"cliui": "^7.0.2",
|
||||||
|
"escalade": "^3.1.1",
|
||||||
|
"get-caller-file": "^2.0.5",
|
||||||
|
"require-directory": "^2.1.1",
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"y18n": "^5.0.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cliui": {
|
||||||
|
"version": "7.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||||
|
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||||
|
"requires": {
|
||||||
|
"string-width": "^4.2.0",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wrap-ansi": "^7.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get-caller-file": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
|
||||||
|
},
|
||||||
|
"y18n": {
|
||||||
|
"version": "5.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||||
|
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@curlconverter/yargs-parser": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@curlconverter/yargs-parser/-/yargs-parser-0.0.1.tgz",
|
||||||
|
"integrity": "sha512-DbEVRYqrorzwqc63MQ3RODflut1tNla8ZCKo1h83lF7+fbntgubZsDfRDBv5Lxj3vkKuvAolysNM2ekwJev8wA=="
|
||||||
|
},
|
||||||
"@dabh/diagnostics": {
|
"@dabh/diagnostics": {
|
||||||
"version": "2.0.3",
|
"version": "2.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||||
|
@ -50563,6 +50769,11 @@
|
||||||
"integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==",
|
"integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"a-sync-waterfall": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA=="
|
||||||
|
},
|
||||||
"abab": {
|
"abab": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||||
|
@ -51132,8 +51343,7 @@
|
||||||
"asap": {
|
"asap": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"asn1": {
|
"asn1": {
|
||||||
"version": "0.2.6",
|
"version": "0.2.6",
|
||||||
|
@ -54331,6 +54541,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"curlconverter": {
|
||||||
|
"version": "3.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/curlconverter/-/curlconverter-3.21.0.tgz",
|
||||||
|
"integrity": "sha512-DXCnp1A/Xa69FujksUfdvWQFAnIn/C+4Wuv8t+UVdZkF/lY5bzj98GGKOGme7V/ckSHDLxE29Xp76sJ5Cpsp5A==",
|
||||||
|
"requires": {
|
||||||
|
"@curlconverter/yargs": "^0.0.2",
|
||||||
|
"cookie": "^0.4.1",
|
||||||
|
"jsesc": "^3.0.2",
|
||||||
|
"nunjucks": "^3.2.3",
|
||||||
|
"query-string": "^7.0.1",
|
||||||
|
"string.prototype.startswith": "^1.0.0",
|
||||||
|
"yamljs": "^0.3.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"jsesc": {
|
||||||
|
"version": "3.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
|
||||||
|
"integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="
|
||||||
|
},
|
||||||
|
"query-string": {
|
||||||
|
"version": "7.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.1.tgz",
|
||||||
|
"integrity": "sha512-MplouLRDHBZSG9z7fpuAAcI7aAYjDLhtsiVZsevsfaHWDS2IDdORKbSd1kWUA+V4zyva/HZoSfpwnYMMQDhb0w==",
|
||||||
|
"requires": {
|
||||||
|
"decode-uri-component": "^0.2.0",
|
||||||
|
"filter-obj": "^1.1.0",
|
||||||
|
"split-on-first": "^1.0.0",
|
||||||
|
"strict-uri-encode": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strict-uri-encode": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"currently-unhandled": {
|
"currently-unhandled": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||||
|
@ -54463,8 +54710,7 @@
|
||||||
"decode-uri-component": {
|
"decode-uri-component": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"dedent": {
|
"dedent": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
|
@ -56777,6 +57023,11 @@
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"filter-obj": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="
|
||||||
|
},
|
||||||
"finalhandler": {
|
"finalhandler": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||||
|
@ -64791,6 +65042,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
@ -66266,6 +66518,23 @@
|
||||||
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
|
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nunjucks": {
|
||||||
|
"version": "3.2.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.3.tgz",
|
||||||
|
"integrity": "sha512-psb6xjLj47+fE76JdZwskvwG4MYsQKXUtMsPh6U0YMvmyjRtKRFcxnlXGWglNybtNTNVmGdp94K62/+NjF5FDQ==",
|
||||||
|
"requires": {
|
||||||
|
"a-sync-waterfall": "^1.0.0",
|
||||||
|
"asap": "^2.0.3",
|
||||||
|
"commander": "^5.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"nwsapi": {
|
"nwsapi": {
|
||||||
"version": "2.2.2",
|
"version": "2.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
||||||
|
@ -70672,6 +70941,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/spex/-/spex-3.2.0.tgz",
|
||||||
"integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg=="
|
"integrity": "sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg=="
|
||||||
},
|
},
|
||||||
|
"split-on-first": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="
|
||||||
|
},
|
||||||
"split-string": {
|
"split-string": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||||
|
@ -71205,6 +71479,15 @@
|
||||||
"es-abstract": "^1.19.1"
|
"es-abstract": "^1.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"string.prototype.startswith": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-VHhsDkuf8gsw4JNRK9cIZjYe6r7PsVUutVohaBhqYAoPaRADoQH+mMgUg7Cs/TgQeDGEvI+PzPEMOdvdsCMvpg==",
|
||||||
|
"requires": {
|
||||||
|
"define-properties": "^1.1.3",
|
||||||
|
"es-abstract": "^1.17.5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"string.prototype.trimend": {
|
"string.prototype.trimend": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||||
|
|
|
@ -124,6 +124,7 @@
|
||||||
"cookie-parser": "^1.4.6",
|
"cookie-parser": "^1.4.6",
|
||||||
"crypto-js": "~4.1.1",
|
"crypto-js": "~4.1.1",
|
||||||
"csrf": "^3.1.0",
|
"csrf": "^3.1.0",
|
||||||
|
"curlconverter": "^3.0.0",
|
||||||
"dotenv": "^8.0.0",
|
"dotenv": "^8.0.0",
|
||||||
"express": "^4.16.4",
|
"express": "^4.16.4",
|
||||||
"express-openapi-validator": "^4.13.6",
|
"express-openapi-validator": "^4.13.6",
|
||||||
|
|
5
packages/cli/src/CurlConverter.d.ts
vendored
Normal file
5
packages/cli/src/CurlConverter.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* eslint-disable @typescript-eslint/naming-convention */
|
||||||
|
/* eslint-disable import/no-default-export */
|
||||||
|
declare module 'curlconverter' {
|
||||||
|
export function toJsonString(data: string): string;
|
||||||
|
}
|
466
packages/cli/src/CurlConverterHelper.ts
Normal file
466
packages/cli/src/CurlConverterHelper.ts
Normal file
|
@ -0,0 +1,466 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||||
|
import curlconverter from 'curlconverter';
|
||||||
|
import get from 'lodash.get';
|
||||||
|
|
||||||
|
interface CurlJson {
|
||||||
|
url: string;
|
||||||
|
raw_url?: string;
|
||||||
|
method: string;
|
||||||
|
contentType?: string;
|
||||||
|
cookies?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
auth?: {
|
||||||
|
user: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
headers?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
files?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
queries: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
data?: {
|
||||||
|
[key: string]: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Parameter {
|
||||||
|
parameterType?: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HttpNodeParameters {
|
||||||
|
url?: string;
|
||||||
|
method: string;
|
||||||
|
sendBody?: boolean;
|
||||||
|
authentication: string;
|
||||||
|
contentType?: 'form-urlencoded' | 'multipart-form-data' | 'json' | 'raw' | 'binaryData';
|
||||||
|
rawContentType?: string;
|
||||||
|
specifyBody?: 'json' | 'keypair';
|
||||||
|
bodyParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
jsonBody?: object;
|
||||||
|
options: {
|
||||||
|
allowUnauthorizedCerts?: boolean;
|
||||||
|
proxy?: string;
|
||||||
|
timeout?: number;
|
||||||
|
redirect: {
|
||||||
|
redirect: {
|
||||||
|
followRedirects?: boolean;
|
||||||
|
maxRedirects?: number;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
response: {
|
||||||
|
response: {
|
||||||
|
fullResponse?: boolean;
|
||||||
|
responseFormat?: string;
|
||||||
|
outputPropertyName?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
sendHeaders?: boolean;
|
||||||
|
headerParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
sendQuery?: boolean;
|
||||||
|
queryParameters?: {
|
||||||
|
parameters: Parameter[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
type HttpNodeHeaders = Pick<HttpNodeParameters, 'sendHeaders' | 'headerParameters'>;
|
||||||
|
|
||||||
|
type HttpNodeQueries = Pick<HttpNodeParameters, 'sendQuery' | 'queryParameters'>;
|
||||||
|
|
||||||
|
enum ContentTypes {
|
||||||
|
applicationJson = 'application/json',
|
||||||
|
applicationFormUrlEncoded = 'application/x-www-form-urlencoded',
|
||||||
|
applicationMultipart = 'multipart/form-data',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUPPORTED_CONTENT_TYPES = [
|
||||||
|
ContentTypes.applicationJson,
|
||||||
|
ContentTypes.applicationFormUrlEncoded,
|
||||||
|
ContentTypes.applicationMultipart,
|
||||||
|
];
|
||||||
|
|
||||||
|
const CONTENT_TYPE_KEY = 'content-type';
|
||||||
|
|
||||||
|
const FOLLOW_REDIRECT_FLAGS = ['--location', '-L'];
|
||||||
|
|
||||||
|
const MAX_REDIRECT_FLAG = '--max-redirs';
|
||||||
|
|
||||||
|
const PROXY_FLAGS = ['-x', '--proxy'];
|
||||||
|
|
||||||
|
const INCLUDE_HEADERS_IN_OUTPUT_FLAGS = ['-i', '--include'];
|
||||||
|
|
||||||
|
const REQUEST_FLAGS = ['-X', '--request'];
|
||||||
|
|
||||||
|
const TIMEOUT_FLAGS = ['--connect-timeout'];
|
||||||
|
|
||||||
|
const DOWNLOAD_FILE_FLAGS = ['-O', '-o'];
|
||||||
|
|
||||||
|
const IGNORE_SSL_ISSUES_FLAGS = ['-k', '--insecure'];
|
||||||
|
|
||||||
|
const curlToJson = (curlCommand: string): CurlJson => {
|
||||||
|
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse
|
||||||
|
return JSON.parse(curlconverter.toJsonString(curlCommand)) as CurlJson;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isContentType = (headers: CurlJson['headers'], contentType: ContentTypes): boolean => {
|
||||||
|
return get(headers, CONTENT_TYPE_KEY) === contentType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isJsonRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationJson)) return true;
|
||||||
|
|
||||||
|
if (curlJson.data) {
|
||||||
|
const bodyKey = Object.keys(curlJson.data)[0];
|
||||||
|
try {
|
||||||
|
JSON.parse(bodyKey);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormUrlEncodedRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationFormUrlEncoded)) return true;
|
||||||
|
if (curlJson.data && !curlJson.files) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isMultipartRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (isContentType(curlJson.headers, ContentTypes.applicationMultipart)) return true;
|
||||||
|
|
||||||
|
// only multipart/form-data request include files
|
||||||
|
if (curlJson.files) return true;
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const isBinaryRequest = (curlJson: CurlJson): boolean => {
|
||||||
|
if (curlJson?.headers?.[CONTENT_TYPE_KEY]) {
|
||||||
|
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY];
|
||||||
|
return ['image', 'video', 'audio'].some((d) => contentType.includes(d));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sanatizeCurlCommand = (curlCommand: string) =>
|
||||||
|
curlCommand
|
||||||
|
.replace(/\r\n/g, ' ')
|
||||||
|
.replace(/\n/g, ' ')
|
||||||
|
.replace(/\\/g, ' ')
|
||||||
|
.replace(/[ ]{2,}/g, ' ');
|
||||||
|
|
||||||
|
const toKeyValueArray = ([key, value]: string[]) => ({ name: key, value });
|
||||||
|
|
||||||
|
const extractHeaders = (headers: CurlJson['headers'] = {}): HttpNodeHeaders => {
|
||||||
|
const emptyHeaders = !Object.keys(headers).length;
|
||||||
|
|
||||||
|
const onlyContentTypeHeaderDefined =
|
||||||
|
Object.keys(headers).length === 1 && headers[CONTENT_TYPE_KEY] !== undefined;
|
||||||
|
|
||||||
|
if (emptyHeaders || onlyContentTypeHeaderDefined) return { sendHeaders: false };
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendHeaders: true,
|
||||||
|
headerParameters: {
|
||||||
|
parameters: Object.entries(headers)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.filter((parameter) => parameter.name !== CONTENT_TYPE_KEY),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractQueries = (queries: CurlJson['queries'] = {}): HttpNodeQueries => {
|
||||||
|
const emptyQueries = !Object.keys(queries).length;
|
||||||
|
|
||||||
|
if (emptyQueries) return { sendQuery: false };
|
||||||
|
|
||||||
|
return {
|
||||||
|
sendQuery: true,
|
||||||
|
queryParameters: {
|
||||||
|
parameters: Object.entries(queries).map(toKeyValueArray),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const extractJson = (body: CurlJson['data']) =>
|
||||||
|
//@ts-ignore
|
||||||
|
// eslint-disable-next-line n8n-local-rules/no-uncaught-json-parse
|
||||||
|
JSON.parse(Object.keys(body)[0]) as { [key: string]: string };
|
||||||
|
|
||||||
|
const jsonBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||||
|
const data = extractJson(body);
|
||||||
|
return Object.entries(data).map(toKeyValueArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const multipartToNodeParameters = (
|
||||||
|
body: CurlJson['data'] = {},
|
||||||
|
files: CurlJson['files'] = {},
|
||||||
|
): Parameter[] | [] => {
|
||||||
|
return [
|
||||||
|
...Object.entries(body)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.map((e) => ({ parameterType: 'formData', ...e })),
|
||||||
|
...Object.entries(files)
|
||||||
|
.map(toKeyValueArray)
|
||||||
|
.map((e) => ({ parameterType: 'formBinaryData', ...e })),
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const keyValueBodyToNodeParameters = (body: CurlJson['data'] = {}): Parameter[] | [] => {
|
||||||
|
return Object.entries(body).map(toKeyValueArray);
|
||||||
|
};
|
||||||
|
|
||||||
|
const lowerCaseContentTypeKey = (obj: { [x: string]: string }): void => {
|
||||||
|
const regex = new RegExp(CONTENT_TYPE_KEY, 'gi');
|
||||||
|
|
||||||
|
const contentTypeKey = Object.keys(obj).find((key) => {
|
||||||
|
const group = Array.from(key.matchAll(regex));
|
||||||
|
if (group.length) return true;
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!contentTypeKey) return;
|
||||||
|
|
||||||
|
const value = obj[contentTypeKey];
|
||||||
|
delete obj[contentTypeKey];
|
||||||
|
obj[CONTENT_TYPE_KEY] = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodeBasicAuthentication = (username: string, password: string) =>
|
||||||
|
Buffer.from(`${username}:${password}`).toString('base64');
|
||||||
|
|
||||||
|
const jsonHasNestedObjects = (json: { [key: string]: string | number | object }) =>
|
||||||
|
Object.values(json).some((e) => typeof e === 'object');
|
||||||
|
|
||||||
|
const extractGroup = (curlCommand: string, regex: RegExp) => curlCommand.matchAll(regex);
|
||||||
|
|
||||||
|
const mapCookies = (cookies: CurlJson['cookies']): { cookie: string } | {} => {
|
||||||
|
if (!cookies) return {};
|
||||||
|
|
||||||
|
const cookiesValues = Object.entries(cookies).reduce(
|
||||||
|
(accumulator: string, entry: [string, string]) => {
|
||||||
|
accumulator += `${entry[0]}=${entry[1]};`;
|
||||||
|
return accumulator;
|
||||||
|
},
|
||||||
|
'',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!cookiesValues) return {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
cookie: cookiesValues,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toHttpNodeParameters = (curlCommand: string): HttpNodeParameters => {
|
||||||
|
const curlJson = curlToJson(curlCommand);
|
||||||
|
|
||||||
|
if (!curlJson.headers) curlJson.headers = {};
|
||||||
|
|
||||||
|
lowerCaseContentTypeKey(curlJson.headers);
|
||||||
|
|
||||||
|
// set basic authentication
|
||||||
|
if (curlJson.auth) {
|
||||||
|
const { user, password: pass } = curlJson.auth;
|
||||||
|
Object.assign(curlJson.headers, {
|
||||||
|
authorization: `Basic ${encodeBasicAuthentication(user, pass)}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const httpNodeParameters: HttpNodeParameters = {
|
||||||
|
url: curlJson.url,
|
||||||
|
authentication: 'none',
|
||||||
|
method: curlJson.method.toUpperCase(),
|
||||||
|
...extractHeaders({ ...curlJson.headers, ...mapCookies(curlJson.cookies) }),
|
||||||
|
...extractQueries(curlJson.queries),
|
||||||
|
options: {
|
||||||
|
redirect: {
|
||||||
|
redirect: {},
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
response: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
//attempt to get the curl flags not supported by the library
|
||||||
|
const curl = sanatizeCurlCommand(curlCommand);
|
||||||
|
|
||||||
|
//check for follow redirect flags
|
||||||
|
if (FOLLOW_REDIRECT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
Object.assign(httpNodeParameters.options.redirect?.redirect, { followRedirects: true });
|
||||||
|
|
||||||
|
if (curl.includes(` ${MAX_REDIRECT_FLAG}`)) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${MAX_REDIRECT_FLAG} (\\d+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, maxRedirects] = extractedValue[0];
|
||||||
|
if (maxRedirects) {
|
||||||
|
Object.assign(httpNodeParameters.options.redirect?.redirect, { maxRedirects });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//check for proxy flags
|
||||||
|
if (PROXY_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = PROXY_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\S*)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, proxy] = extractedValue[0];
|
||||||
|
Object.assign(httpNodeParameters.options, { proxy });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for "include header in output" flag
|
||||||
|
if (INCLUDE_HEADERS_IN_OUTPUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
Object.assign(httpNodeParameters.options?.response?.response, {
|
||||||
|
fullResponse: true,
|
||||||
|
responseFormat: 'autodetect',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for request flag
|
||||||
|
if (REQUEST_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = REQUEST_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\w+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, request] = extractedValue[0];
|
||||||
|
httpNodeParameters.method = request.toUpperCase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for timeout flag
|
||||||
|
if (TIMEOUT_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = TIMEOUT_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
const extractedValue = Array.from(
|
||||||
|
extractGroup(curl, new RegExp(` ${foundFlag} (\\d+)`, 'g')),
|
||||||
|
);
|
||||||
|
if (extractedValue.length) {
|
||||||
|
const [_, timeout] = extractedValue[0];
|
||||||
|
Object.assign(httpNodeParameters.options, {
|
||||||
|
timeout: parseInt(timeout, 10) * 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check for download flag
|
||||||
|
if (DOWNLOAD_FILE_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = DOWNLOAD_FILE_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
Object.assign(httpNodeParameters.options.response.response, {
|
||||||
|
responseFormat: 'file',
|
||||||
|
outputPropertyName: 'data',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IGNORE_SSL_ISSUES_FLAGS.some((flag) => curl.includes(` ${flag}`))) {
|
||||||
|
const foundFlag = IGNORE_SSL_ISSUES_FLAGS.find((flag) => curl.includes(` ${flag}`));
|
||||||
|
if (foundFlag) {
|
||||||
|
Object.assign(httpNodeParameters.options, {
|
||||||
|
allowUnauthorizedCerts: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType = curlJson?.headers?.[CONTENT_TYPE_KEY] as ContentTypes;
|
||||||
|
|
||||||
|
if (isBinaryRequest(curlJson)) {
|
||||||
|
return Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'binaryData',
|
||||||
|
sendBody: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (contentType && !SUPPORTED_CONTENT_TYPES.includes(contentType)) {
|
||||||
|
return Object.assign(httpNodeParameters, {
|
||||||
|
sendBody: true,
|
||||||
|
contentType: 'raw',
|
||||||
|
rawContentType: contentType,
|
||||||
|
body: Object.keys(curlJson?.data ?? {})[0],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isJsonRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'json',
|
||||||
|
sendBody: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = extractJson(curlJson.data);
|
||||||
|
|
||||||
|
if (jsonHasNestedObjects(json)) {
|
||||||
|
// json body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
specifyBody: 'json',
|
||||||
|
jsonBody: JSON.stringify(json),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// key-value body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
specifyBody: 'keypair',
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: jsonBodyToNodeParameters(curlJson.data),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if (isFormUrlEncodedRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'form-urlencoded',
|
||||||
|
sendBody: true,
|
||||||
|
specifyBody: 'keypair',
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: keyValueBodyToNodeParameters(curlJson.data),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else if (isMultipartRequest(curlJson)) {
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
contentType: 'multipart-form-data',
|
||||||
|
sendBody: true,
|
||||||
|
bodyParameters: {
|
||||||
|
parameters: multipartToNodeParameters(curlJson.data, curlJson.files),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// could not figure the content type so do not set the body
|
||||||
|
Object.assign(httpNodeParameters, {
|
||||||
|
sendBody: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(httpNodeParameters.options?.redirect.redirect).length) {
|
||||||
|
// @ts-ignore
|
||||||
|
delete httpNodeParameters.options.redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Object.keys(httpNodeParameters.options.response.response).length) {
|
||||||
|
// @ts-ignore
|
||||||
|
delete httpNodeParameters.options.response;
|
||||||
|
}
|
||||||
|
|
||||||
|
return httpNodeParameters;
|
||||||
|
};
|
|
@ -221,3 +221,13 @@ export function unflattenExecutionData(fullExecutionData: IExecutionFlattedDb):
|
||||||
|
|
||||||
return returnData;
|
return returnData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const flattenObject = (obj: { [x: string]: any }, prefix = '') =>
|
||||||
|
Object.keys(obj).reduce((acc, k) => {
|
||||||
|
const pre = prefix.length ? prefix + '.' : '';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
|
if (typeof obj[k] === 'object') Object.assign(acc, flattenObject(obj[k], pre + k));
|
||||||
|
//@ts-ignore
|
||||||
|
else acc[pre + k] = obj[k];
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
|
@ -43,6 +43,7 @@ import { FindManyOptions, getConnectionManager, In } from 'typeorm';
|
||||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||||
import axios, { AxiosRequestConfig } from 'axios';
|
import axios, { AxiosRequestConfig } from 'axios';
|
||||||
import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
||||||
|
import curlconverter from 'curlconverter';
|
||||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||||
import { compare } from 'bcryptjs';
|
import { compare } from 'bcryptjs';
|
||||||
|
@ -96,6 +97,7 @@ import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants';
|
||||||
import { credentialsController } from './credentials/credentials.controller';
|
import { credentialsController } from './credentials/credentials.controller';
|
||||||
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
||||||
import type {
|
import type {
|
||||||
|
CurlHelper,
|
||||||
ExecutionRequest,
|
ExecutionRequest,
|
||||||
NodeListSearchRequest,
|
NodeListSearchRequest,
|
||||||
NodeParameterOptionsRequest,
|
NodeParameterOptionsRequest,
|
||||||
|
@ -151,6 +153,8 @@ import {
|
||||||
import glob from 'fast-glob';
|
import glob from 'fast-glob';
|
||||||
import { ResponseError } from './ResponseHelper';
|
import { ResponseError } from './ResponseHelper';
|
||||||
|
|
||||||
|
import { toHttpNodeParameters } from './CurlConverterHelper';
|
||||||
|
|
||||||
require('body-parser-xml')(bodyParser);
|
require('body-parser-xml')(bodyParser);
|
||||||
|
|
||||||
const exec = promisify(callbackExec);
|
const exec = promisify(callbackExec);
|
||||||
|
@ -1061,6 +1065,27 @@ class App {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// curl-converter
|
||||||
|
// ----------------------------------------
|
||||||
|
this.app.post(
|
||||||
|
`/${this.restEndpoint}/curl-to-json`,
|
||||||
|
ResponseHelper.send(
|
||||||
|
async (
|
||||||
|
req: CurlHelper.ToJson,
|
||||||
|
res: express.Response,
|
||||||
|
): Promise<{ [key: string]: string }> => {
|
||||||
|
const curlCommand = req.body.curlCommand ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parameters = toHttpNodeParameters(curlCommand);
|
||||||
|
return ResponseHelper.flattenObject(parameters, 'parameters');
|
||||||
|
} catch (e) {
|
||||||
|
throw new ResponseHelper.ResponseError(`Invalid cURL command`, undefined, 400);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Credential-Types
|
// Credential-Types
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
8
packages/cli/src/requests.d.ts
vendored
8
packages/cli/src/requests.d.ts
vendored
|
@ -327,3 +327,11 @@ export declare namespace NodeRequest {
|
||||||
|
|
||||||
type Update = Post;
|
type Update = Post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------
|
||||||
|
// /curl-to-json
|
||||||
|
// ----------------------------------
|
||||||
|
|
||||||
|
export declare namespace CurlHelper {
|
||||||
|
type ToJson = AuthenticatedRequest<{}, {}, { curlCommand?: string }>;
|
||||||
|
}
|
||||||
|
|
275
packages/cli/test/unit/CurlConverterHelper.test.ts
Normal file
275
packages/cli/test/unit/CurlConverterHelper.test.ts
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
import { toHttpNodeParameters } from "../../src/CurlConverterHelper";
|
||||||
|
|
||||||
|
describe('CurlConverterHelper', () => {
|
||||||
|
|
||||||
|
test('Should parse form-urlencoded content type correctly', () => {
|
||||||
|
const curl = 'curl -X POST https://reqbin.com/echo/post/form -H "Content-Type: application/x-www-form-urlencoded" -d "param1=value1¶m2=value2"';
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/form');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('param1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('param2');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].value).toBe('value2');
|
||||||
|
expect(parameters.contentType).toBe('form-urlencoded');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse JSON content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -H 'Content-Type: application/json' -d '{"login":"my_login","password":"my_password"}'`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('login');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('my_login');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('password');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].value).toBe('my_password');
|
||||||
|
expect(parameters.contentType).toBe('json');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse multipart-form-data content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||||
|
expect(parameters.contentType).toBe('multipart-form-data');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse binary request correctly', () => {
|
||||||
|
const curl = `curl --location --request POST 'https://www.website.com' --header 'Content-Type: image/png' --data-binary '@/Users/image.png`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
console.log(JSON.stringify(parameters, undefined, 2));
|
||||||
|
expect(parameters.url).toBe('https://www.website.com');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.contentType).toBe('binaryData');
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse unknown content type correctly', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/xml
|
||||||
|
-H "Content-Type: application/xml"
|
||||||
|
-H "Accept: application/xml"
|
||||||
|
-d "<Request><Login>my_login</Login><Password>my_password</Password></Request>"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/xml');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.contentType).toBe('raw');
|
||||||
|
expect(parameters.rawContentType).toBe('application/xml');
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('Accept');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe('application/xml');
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse header properties and keep the original case', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo/post/json -v -F key1=value1 -F upload=@localfilename -H "ACCEPT: text/javascript" -H "content-type: multipart/form-data"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo/post/json');
|
||||||
|
expect(parameters.sendBody).toBe(true);
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].parameterType).toBe('formData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].name).toBe('key1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[0].value).toBe('value1');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].parameterType).toBe('formBinaryData');
|
||||||
|
expect(parameters.bodyParameters?.parameters[1].name).toBe('upload');
|
||||||
|
expect(parameters.contentType).toBe('multipart-form-data');
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('ACCEPT');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe('text/javascript');
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse querystring properties', () => {
|
||||||
|
const curl = `curl -G -d 'q=kitties' -d 'count=20' https://google.com/search`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://google.com/search');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendHeaders).toBe(false);
|
||||||
|
expect(parameters.sendQuery).toBe(true);
|
||||||
|
expect(parameters.queryParameters?.parameters[0].name).toBe('q');
|
||||||
|
expect(parameters.queryParameters?.parameters[0].value).toBe('kitties');
|
||||||
|
expect(parameters.queryParameters?.parameters[1].name).toBe('count');
|
||||||
|
expect(parameters.queryParameters?.parameters[1].value).toBe('20');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse basic authentication property and keep the original case', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password"`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location flag with --location', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --location`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location flag with --L', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -L`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse location and max redirects flags with --location and --max-redirs 10', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --location --max-redirs 10`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.redirect.redirect.followRedirects).toBe(true);
|
||||||
|
expect(parameters.options.redirect.redirect.maxRedirects).toBe("10");
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse proxy flag -x', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.proxy).toBe('https://google.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse proxy flag --proxy', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.proxy).toBe('https://google.com');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include headers on output flag --include', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" --include -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include headers on output flag -i', () => {
|
||||||
|
const curl = `curl https://reqbin.com/echo -u "login:password" -x https://google.com -i`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.contentType).toBeUndefined();
|
||||||
|
expect(parameters.sendQuery).toBe(false);
|
||||||
|
expect(parameters.sendHeaders).toBe(true);
|
||||||
|
expect(parameters.headerParameters?.parameters[0].name).toBe('authorization');
|
||||||
|
expect(parameters.headerParameters?.parameters[0].value).toBe(`Basic ${Buffer.from('login:password').toString('base64')}`);
|
||||||
|
expect(parameters.options.response.response.fullResponse).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include request flag -X', () => {
|
||||||
|
const curl = `curl -X POST https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include request flag --request', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -x https://google.com`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse include timeout flag --connect-timeout', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --connect-timeout 20`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.timeout).toBe(20000);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Should parse download file flag -O', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -O`;
|
||||||
|
; const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||||
|
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse download file flag -o', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -o`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.response.response.responseFormat).toBe('file');
|
||||||
|
expect(parameters.options.response.response.outputPropertyName).toBe('data');
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse ignore SSL flag -k', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" -k`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Should parse ignore SSL flag --insecure', () => {
|
||||||
|
const curl = `curl --request POST https://reqbin.com/echo -u "login:password" --insecure`;
|
||||||
|
const parameters = toHttpNodeParameters(curl);
|
||||||
|
expect(parameters.url).toBe('https://reqbin.com/echo');
|
||||||
|
expect(parameters.method).toBe('POST');
|
||||||
|
expect(parameters.sendBody).toBe(false);
|
||||||
|
expect(parameters.options.allowUnauthorizedCerts).toBe(true);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
|
@ -127,7 +127,7 @@ export interface IEndpointOptions {
|
||||||
export interface IUpdateInformation {
|
export interface IUpdateInformation {
|
||||||
name: string;
|
name: string;
|
||||||
key: string;
|
key: string;
|
||||||
value: string | number; // with null makes problems in NodeSettings.vue
|
value: string | number | { [key: string]: string | number | boolean }; // with null makes problems in NodeSettings.vue
|
||||||
node?: string;
|
node?: string;
|
||||||
oldValue?: string | number;
|
oldValue?: string | number;
|
||||||
}
|
}
|
||||||
|
@ -924,6 +924,8 @@ export interface IModalState {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
mode?: string | null;
|
mode?: string | null;
|
||||||
activeId?: string | null;
|
activeId?: string | null;
|
||||||
|
curlCommand?: string;
|
||||||
|
httpNodeParameters?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
|
export type IRunDataDisplayMode = 'table' | 'json' | 'binary';
|
||||||
|
|
6
packages/editor-ui/src/api/curlHelper.ts
Normal file
6
packages/editor-ui/src/api/curlHelper.ts
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import {IRestApiContext} from "@/Interface";
|
||||||
|
import {makeRestApiRequest} from "@/api/helpers";
|
||||||
|
|
||||||
|
export function getCurlToJson(context: IRestApiContext, curlCommand: string): Promise<{ curlCommand: string | null }> {
|
||||||
|
return makeRestApiRequest(context, 'POST', '/curl-to-json', { curlCommand });
|
||||||
|
}
|
196
packages/editor-ui/src/components/ImportCurlModal.vue
Normal file
196
packages/editor-ui/src/components/ImportCurlModal.vue
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
<template>
|
||||||
|
<Modal
|
||||||
|
width="700px"
|
||||||
|
:title="$locale.baseText('importCurlModal.title')"
|
||||||
|
:eventBus="modalBus"
|
||||||
|
:name="IMPORT_CURL_MODAL_KEY"
|
||||||
|
:center="true"
|
||||||
|
>
|
||||||
|
<template slot="content">
|
||||||
|
<div :class="$style.container">
|
||||||
|
<n8n-input-label :label="$locale.baseText('importCurlModal.input.label')">
|
||||||
|
<n8n-input
|
||||||
|
:value="curlCommand"
|
||||||
|
type="textarea"
|
||||||
|
:rows="5"
|
||||||
|
:placeholder="$locale.baseText('importCurlModal.input.placeholder')"
|
||||||
|
@input="onInput"
|
||||||
|
@focus="$event.target.select()"
|
||||||
|
ref="input"
|
||||||
|
/>
|
||||||
|
</n8n-input-label>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template slot="footer">
|
||||||
|
<div :class="$style.modalFooter">
|
||||||
|
<n8n-notice
|
||||||
|
:class="$style.notice"
|
||||||
|
:content="$locale.baseText('ImportCurlModal.notice.content')"
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<n8n-button
|
||||||
|
@click="importCurlCommand"
|
||||||
|
float="right"
|
||||||
|
:label="$locale.baseText('importCurlModal.button.label')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import Vue from 'vue';
|
||||||
|
import Modal from './Modal.vue';
|
||||||
|
import {
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
|
CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS,
|
||||||
|
CURL_IMPORT_NODES_PROTOCOLS,
|
||||||
|
} from '../constants';
|
||||||
|
import { showMessage } from './mixins/showMessage';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { INodeUi } from '@/Interface';
|
||||||
|
|
||||||
|
export default mixins(showMessage).extend({
|
||||||
|
name: 'ImportCurlModal',
|
||||||
|
components: {
|
||||||
|
Modal,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
|
curlCommand: '',
|
||||||
|
modalBus: new Vue(),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
node(): INodeUi {
|
||||||
|
return this.$store.getters.activeNode;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDialog(): void {
|
||||||
|
this.modalBus.$emit('close');
|
||||||
|
},
|
||||||
|
onInput(value: string): void {
|
||||||
|
this.curlCommand = value;
|
||||||
|
},
|
||||||
|
async importCurlCommand(): Promise<void> {
|
||||||
|
const curlCommand = this.curlCommand;
|
||||||
|
if (curlCommand === '') return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parameters = await this.$store.dispatch('ui/getCurlToJson', curlCommand);
|
||||||
|
|
||||||
|
const url = parameters['parameters.url'];
|
||||||
|
|
||||||
|
const invalidProtocol = CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS.find((p) =>
|
||||||
|
url.includes(`${p}://`),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!invalidProtocol) {
|
||||||
|
this.$store.dispatch('ui/setHttpNodeParameters', {
|
||||||
|
parameters: JSON.stringify(parameters),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.closeDialog();
|
||||||
|
|
||||||
|
this.sendTelemetry();
|
||||||
|
|
||||||
|
return;
|
||||||
|
// if we have a node that supports the invalid protocol
|
||||||
|
// suggest that one
|
||||||
|
} else if (CURL_IMPORT_NODES_PROTOCOLS[invalidProtocol]) {
|
||||||
|
const useNode = CURL_IMPORT_NODES_PROTOCOLS[invalidProtocol];
|
||||||
|
|
||||||
|
this.showProtocolErrorWithSupportedNode(invalidProtocol, useNode);
|
||||||
|
// we do not have a node that supports the use protocol
|
||||||
|
} else {
|
||||||
|
this.showProtocolError(invalidProtocol);
|
||||||
|
}
|
||||||
|
this.sendTelemetry({ success: false, invalidProtocol: true, protocol: invalidProtocol });
|
||||||
|
} catch (e) {
|
||||||
|
this.showInvalidcURLCommandError();
|
||||||
|
|
||||||
|
this.sendTelemetry({ success: false, invalidProtocol: false });
|
||||||
|
} finally {
|
||||||
|
this.$store.dispatch('ui/setCurlCommand', { command: this.curlCommand });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showProtocolErrorWithSupportedNode(protocol: string, node: string): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidProtocol1.title', {
|
||||||
|
interpolate: {
|
||||||
|
node,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidProtocol.message', {
|
||||||
|
interpolate: {
|
||||||
|
protocol: protocol.toUpperCase(),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showProtocolError(protocol: string): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidProtocol2.title'),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidProtocol.message', {
|
||||||
|
interpolate: {
|
||||||
|
protocol,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
showInvalidcURLCommandError(): void {
|
||||||
|
this.$showToast({
|
||||||
|
title: this.$locale.baseText('importParameter.showError.invalidCurlCommand.title'),
|
||||||
|
message: this.$locale.baseText('importParameter.showError.invalidCurlCommand.message'),
|
||||||
|
type: 'error',
|
||||||
|
duration: 0,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendTelemetry(
|
||||||
|
data: { success: boolean; invalidProtocol: boolean; protocol?: string } = {
|
||||||
|
success: true,
|
||||||
|
invalidProtocol: false,
|
||||||
|
protocol: '',
|
||||||
|
},
|
||||||
|
): void {
|
||||||
|
this.$telemetry.track('User imported curl command', {
|
||||||
|
success: data.success,
|
||||||
|
invalidProtocol: data.invalidProtocol,
|
||||||
|
protocol: data.protocol,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.curlCommand = this.$store.getters['ui/getCurlCommand'];
|
||||||
|
setTimeout(() => {
|
||||||
|
(this.$refs.input as HTMLTextAreaElement).focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.modalFooter {
|
||||||
|
justify-content: space-between;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notice {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container > * {
|
||||||
|
margin-bottom: var(--spacing-s);
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
33
packages/editor-ui/src/components/ImportParameter.vue
Normal file
33
packages/editor-ui/src/components/ImportParameter.vue
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<template>
|
||||||
|
<div :class="$style.importSection">
|
||||||
|
<n8n-button
|
||||||
|
type="secondary"
|
||||||
|
:label="$locale.baseText('importParameter.label')"
|
||||||
|
size="mini"
|
||||||
|
@click="onImportCurlClicked"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { IMPORT_CURL_MODAL_KEY } from '@/constants';
|
||||||
|
import mixins from 'vue-typed-mixins';
|
||||||
|
import { showMessage } from './mixins/showMessage';
|
||||||
|
|
||||||
|
export default mixins(showMessage).extend({
|
||||||
|
name: 'import-parameter',
|
||||||
|
methods: {
|
||||||
|
onImportCurlClicked() {
|
||||||
|
this.$store.dispatch('ui/openModal', IMPORT_CURL_MODAL_KEY);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style module lang="scss">
|
||||||
|
.importSection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -93,6 +93,10 @@
|
||||||
<CommunityPackageInstallModal />
|
<CommunityPackageInstallModal />
|
||||||
</ModalRoot>
|
</ModalRoot>
|
||||||
|
|
||||||
|
<ModalRoot :name="IMPORT_CURL_MODAL_KEY">
|
||||||
|
<ImportCurlModal />
|
||||||
|
</ModalRoot>
|
||||||
|
|
||||||
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
||||||
<template v-slot="{ modalName, activeId, mode }">
|
<template v-slot="{ modalName, activeId, mode }">
|
||||||
<CommunityPackageManageConfirmModal
|
<CommunityPackageManageConfirmModal
|
||||||
|
@ -128,6 +132,7 @@ import {
|
||||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||||
WORKFLOW_OPEN_MODAL_KEY,
|
WORKFLOW_OPEN_MODAL_KEY,
|
||||||
WORKFLOW_SETTINGS_MODAL_KEY,
|
WORKFLOW_SETTINGS_MODAL_KEY,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
|
|
||||||
import AboutModal from './AboutModal.vue';
|
import AboutModal from './AboutModal.vue';
|
||||||
|
@ -150,6 +155,7 @@ import WorkflowOpen from "./WorkflowOpen.vue";
|
||||||
import DeleteUserModal from "./DeleteUserModal.vue";
|
import DeleteUserModal from "./DeleteUserModal.vue";
|
||||||
import ExecutionsList from "./ExecutionsList.vue";
|
import ExecutionsList from "./ExecutionsList.vue";
|
||||||
import ActivationModal from "./ActivationModal.vue";
|
import ActivationModal from "./ActivationModal.vue";
|
||||||
|
import ImportCurlModal from './ImportCurlModal.vue';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
name: "Modals",
|
name: "Modals",
|
||||||
|
@ -174,6 +180,7 @@ export default Vue.extend({
|
||||||
ValueSurvey,
|
ValueSurvey,
|
||||||
WorkflowSettings,
|
WorkflowSettings,
|
||||||
WorkflowOpen,
|
WorkflowOpen,
|
||||||
|
ImportCurlModal,
|
||||||
},
|
},
|
||||||
data: () => ({
|
data: () => ({
|
||||||
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
|
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
|
||||||
|
@ -195,6 +202,7 @@ export default Vue.extend({
|
||||||
VALUE_SURVEY_MODAL_KEY,
|
VALUE_SURVEY_MODAL_KEY,
|
||||||
EXECUTIONS_MODAL_KEY,
|
EXECUTIONS_MODAL_KEY,
|
||||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -302,6 +302,7 @@ import {
|
||||||
|
|
||||||
import CodeEdit from '@/components/CodeEdit.vue';
|
import CodeEdit from '@/components/CodeEdit.vue';
|
||||||
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
||||||
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
||||||
import NodeCredentials from '@/components/NodeCredentials.vue';
|
import NodeCredentials from '@/components/NodeCredentials.vue';
|
||||||
import ScopesNotice from '@/components/ScopesNotice.vue';
|
import ScopesNotice from '@/components/ScopesNotice.vue';
|
||||||
|
@ -341,6 +342,7 @@ export default mixins(
|
||||||
ParameterIssues,
|
ParameterIssues,
|
||||||
ResourceLocator,
|
ResourceLocator,
|
||||||
TextEdit,
|
TextEdit,
|
||||||
|
ImportParameter,
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'inputSize',
|
'inputSize',
|
||||||
|
|
|
@ -16,6 +16,11 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<import-parameter
|
||||||
|
v-else-if="parameter.type === 'curlImport' && nodeTypeName === 'n8n-nodes-base.httpRequest' && nodeTypeVersion >= 3"
|
||||||
|
@valueChanged="valueChanged"
|
||||||
|
/>
|
||||||
|
|
||||||
<n8n-notice
|
<n8n-notice
|
||||||
v-else-if="parameter.type === 'notice'"
|
v-else-if="parameter.type === 'notice'"
|
||||||
class="parameter-item"
|
class="parameter-item"
|
||||||
|
@ -93,7 +98,6 @@
|
||||||
import {
|
import {
|
||||||
INodeParameters,
|
INodeParameters,
|
||||||
INodeProperties,
|
INodeProperties,
|
||||||
INodeType,
|
|
||||||
INodeTypeDescription,
|
INodeTypeDescription,
|
||||||
NodeParameterValue,
|
NodeParameterValue,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
|
@ -104,6 +108,7 @@ import MultipleParameter from '@/components/MultipleParameter.vue';
|
||||||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||||
|
import ImportParameter from '@/components/ImportParameter.vue';
|
||||||
|
|
||||||
import { get, set } from 'lodash';
|
import { get, set } from 'lodash';
|
||||||
|
|
||||||
|
@ -121,6 +126,7 @@ export default mixins(
|
||||||
ParameterInputFull,
|
ParameterInputFull,
|
||||||
FixedCollectionParameter: () => import('./FixedCollectionParameter.vue') as Promise<Component>,
|
FixedCollectionParameter: () => import('./FixedCollectionParameter.vue') as Promise<Component>,
|
||||||
CollectionParameter: () => import('./CollectionParameter.vue') as Promise<Component>,
|
CollectionParameter: () => import('./CollectionParameter.vue') as Promise<Component>,
|
||||||
|
ImportParameter,
|
||||||
},
|
},
|
||||||
props: [
|
props: [
|
||||||
'nodeValues', // INodeParameters
|
'nodeValues', // INodeParameters
|
||||||
|
@ -130,6 +136,18 @@ export default mixins(
|
||||||
'indent',
|
'indent',
|
||||||
],
|
],
|
||||||
computed: {
|
computed: {
|
||||||
|
nodeTypeVersion(): number | null {
|
||||||
|
if (this.node) {
|
||||||
|
return this.node.typeVersion;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
nodeTypeName (): string {
|
||||||
|
if (this.node) {
|
||||||
|
return this.node.type;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
filteredParameters (): INodeProperties[] {
|
filteredParameters (): INodeProperties[] {
|
||||||
return this.parameters.filter((parameter: INodeProperties) => this.displayNodeParameter(parameter));
|
return this.parameters.filter((parameter: INodeProperties) => this.displayNodeParameter(parameter));
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,7 @@ export const WORKFLOW_ACTIVE_MODAL_KEY = 'activation';
|
||||||
export const ONBOARDING_CALL_SIGNUP_MODAL_KEY = 'onboardingCallSignup';
|
export const ONBOARDING_CALL_SIGNUP_MODAL_KEY = 'onboardingCallSignup';
|
||||||
export const COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
export const COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
||||||
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
||||||
|
export const IMPORT_CURL_MODAL_KEY = 'importCurl';
|
||||||
|
|
||||||
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
||||||
UNINSTALL: 'uninstall',
|
UNINSTALL: 'uninstall',
|
||||||
|
@ -350,3 +351,36 @@ export enum EnterpriseEditionFeature {
|
||||||
Sharing = 'sharing',
|
Sharing = 'sharing',
|
||||||
}
|
}
|
||||||
export const MAIN_NODE_PANEL_WIDTH = 360;
|
export const MAIN_NODE_PANEL_WIDTH = 360;
|
||||||
|
|
||||||
|
export const CURL_IMPORT_NOT_SUPPORTED_PROTOCOLS = [
|
||||||
|
'ftp',
|
||||||
|
'ftps',
|
||||||
|
'dict',
|
||||||
|
'imap',
|
||||||
|
'imaps',
|
||||||
|
'ldap',
|
||||||
|
'ldaps',
|
||||||
|
'mqtt',
|
||||||
|
'pop',
|
||||||
|
'pop3s',
|
||||||
|
'rtmp',
|
||||||
|
'rtsp',
|
||||||
|
'scp',
|
||||||
|
'sftp',
|
||||||
|
'smb',
|
||||||
|
'smbs',
|
||||||
|
'smtp',
|
||||||
|
'smtps',
|
||||||
|
'telnet',
|
||||||
|
'tftp',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CURL_IMPORT_NODES_PROTOCOLS: { [key: string]: string } = {
|
||||||
|
'ftp': 'FTP',
|
||||||
|
'ftps': 'FTP',
|
||||||
|
'ldap': 'LDAP',
|
||||||
|
'ldaps': 'LDAP',
|
||||||
|
'mqtt': 'MQTT',
|
||||||
|
'imap': 'IMAP',
|
||||||
|
'imaps': 'IMAP',
|
||||||
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@ import Vue from 'vue';
|
||||||
import {CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, VALUE_SURVEY_MODAL_KEY} from '@/constants';
|
import {CONTACT_PROMPT_MODAL_KEY, EnterpriseEditionFeature, VALUE_SURVEY_MODAL_KEY} from '@/constants';
|
||||||
import { ITelemetrySettings } from 'n8n-workflow';
|
import { ITelemetrySettings } from 'n8n-workflow';
|
||||||
import { testHealthEndpoint } from '@/api/templates';
|
import { testHealthEndpoint } from '@/api/templates';
|
||||||
import {createApiKey, deleteApiKey, getApiKey} from "@/api/api-keys";
|
import {createApiKey, deleteApiKey, getApiKey } from "@/api/api-keys";
|
||||||
|
|
||||||
const module: Module<ISettingsState, IRootState> = {
|
const module: Module<ISettingsState, IRootState> = {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
@ -183,6 +183,7 @@ const module: Module<ISettingsState, IRootState> = {
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async submitValueSurvey(context: ActionContext<ISettingsState, IRootState>, params: IN8nValueSurveyData) {
|
async submitValueSurvey(context: ActionContext<ISettingsState, IRootState>, params: IN8nValueSurveyData) {
|
||||||
try {
|
try {
|
||||||
const instanceId = context.state.settings.instanceId;
|
const instanceId = context.state.settings.instanceId;
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { getCurlToJson } from '@/api/curlHelper';
|
||||||
import { applyForOnboardingCall, fetchNextOnboardingPrompt, submitEmailOnSignup } from '@/api/workflow-webhooks';
|
import { applyForOnboardingCall, fetchNextOnboardingPrompt, submitEmailOnSignup } from '@/api/workflow-webhooks';
|
||||||
import {
|
import {
|
||||||
ABOUT_MODAL_KEY,
|
ABOUT_MODAL_KEY,
|
||||||
|
@ -22,6 +23,7 @@ import {
|
||||||
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
||||||
FAKE_DOOR_FEATURES,
|
FAKE_DOOR_FEATURES,
|
||||||
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
||||||
|
IMPORT_CURL_MODAL_KEY,
|
||||||
} from '@/constants';
|
} from '@/constants';
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import { ActionContext, Module } from 'vuex';
|
import { ActionContext, Module } from 'vuex';
|
||||||
|
@ -100,6 +102,11 @@ const module: Module<IUiState, IRootState> = {
|
||||||
mode: '',
|
mode: '',
|
||||||
activeId: null,
|
activeId: null,
|
||||||
},
|
},
|
||||||
|
[IMPORT_CURL_MODAL_KEY]: {
|
||||||
|
open: false,
|
||||||
|
curlCommand: '',
|
||||||
|
httpNodeParameters: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
modalStack: [],
|
modalStack: [],
|
||||||
sidebarMenuCollapsed: true,
|
sidebarMenuCollapsed: true,
|
||||||
|
@ -167,6 +174,12 @@ const module: Module<IUiState, IRootState> = {
|
||||||
isVersionsOpen: (state: IUiState) => {
|
isVersionsOpen: (state: IUiState) => {
|
||||||
return state.modals[VERSIONS_MODAL_KEY].open;
|
return state.modals[VERSIONS_MODAL_KEY].open;
|
||||||
},
|
},
|
||||||
|
getCurlCommand: (state: IUiState) => {
|
||||||
|
return state.modals[IMPORT_CURL_MODAL_KEY].curlCommand;
|
||||||
|
},
|
||||||
|
getHttpNodeParameters: (state: IUiState) => {
|
||||||
|
return state.modals[IMPORT_CURL_MODAL_KEY].httpNodeParameters;
|
||||||
|
},
|
||||||
isModalOpen: (state: IUiState) => {
|
isModalOpen: (state: IUiState) => {
|
||||||
return (name: string) => state.modals[name].open;
|
return (name: string) => state.modals[name].open;
|
||||||
},
|
},
|
||||||
|
@ -231,6 +244,14 @@ const module: Module<IUiState, IRootState> = {
|
||||||
const { name, id } = params;
|
const { name, id } = params;
|
||||||
Vue.set(state.modals[name], 'activeId', id);
|
Vue.set(state.modals[name], 'activeId', id);
|
||||||
},
|
},
|
||||||
|
setCurlCommand: (state: IUiState, params: {name: string, command: string}) => {
|
||||||
|
const { name, command } = params;
|
||||||
|
Vue.set(state.modals[name], 'curlCommand', command);
|
||||||
|
},
|
||||||
|
setHttpNodeParameters: (state: IUiState, params: {name: string, parameters: string}) => {
|
||||||
|
const { name, parameters } = params;
|
||||||
|
Vue.set(state.modals[name], 'httpNodeParameters', parameters);
|
||||||
|
},
|
||||||
openModal: (state: IUiState, name: string) => {
|
openModal: (state: IUiState, name: string) => {
|
||||||
Vue.set(state.modals[name], 'open', true);
|
Vue.set(state.modals[name], 'open', true);
|
||||||
state.modalStack = [name].concat(state.modalStack);
|
state.modalStack = [name].concat(state.modalStack);
|
||||||
|
@ -323,6 +344,17 @@ const module: Module<IUiState, IRootState> = {
|
||||||
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
||||||
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
},
|
},
|
||||||
|
setCurlCommand: async (context: ActionContext<IUiState, IRootState>, { command }: {command: string}) => {
|
||||||
|
context.commit('setCurlCommand', { name: IMPORT_CURL_MODAL_KEY, command });
|
||||||
|
},
|
||||||
|
setHttpNodeParameters: async (context: ActionContext<IUiState, IRootState>, { parameters }) => {
|
||||||
|
context.commit('setHttpNodeParameters', { name: IMPORT_CURL_MODAL_KEY, parameters });
|
||||||
|
},
|
||||||
|
openExisitngCredential: async (context: ActionContext<IUiState, IRootState>, { id }: {id: string}) => {
|
||||||
|
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id });
|
||||||
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'edit' });
|
||||||
|
context.commit('openModal', CREDENTIAL_EDIT_MODAL_KEY);
|
||||||
|
},
|
||||||
openNewCredential: async (context: ActionContext<IUiState, IRootState>, { type }: {type: string}) => {
|
openNewCredential: async (context: ActionContext<IUiState, IRootState>, { type }: {type: string}) => {
|
||||||
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id: type });
|
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id: type });
|
||||||
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'new' });
|
context.commit('setMode', { name: CREDENTIAL_EDIT_MODAL_KEY, mode: 'new' });
|
||||||
|
@ -354,6 +386,9 @@ const module: Module<IUiState, IRootState> = {
|
||||||
context.commit('setMode', { name: COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, mode: COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE });
|
context.commit('setMode', { name: COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY, mode: COMMUNITY_PACKAGE_MANAGE_ACTIONS.UPDATE });
|
||||||
context.commit('openModal', COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
context.commit('openModal', COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY);
|
||||||
},
|
},
|
||||||
|
async getCurlToJson(context: ActionContext<IUiState, IRootState>, curlCommand) {
|
||||||
|
return await getCurlToJson(context.rootGetters['getRestApiContext'], curlCommand);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1114,5 +1114,16 @@
|
||||||
"workflowSettings.showMessage.saveSettings.title": "Workflow settings saved",
|
"workflowSettings.showMessage.saveSettings.title": "Workflow settings saved",
|
||||||
"workflowSettings.timeoutAfter": "Timeout After",
|
"workflowSettings.timeoutAfter": "Timeout After",
|
||||||
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
"workflowSettings.timeoutWorkflow": "Timeout Workflow",
|
||||||
"workflowSettings.timezone": "Timezone"
|
"workflowSettings.timezone": "Timezone",
|
||||||
|
"importCurlModal.title": "Import cURL command",
|
||||||
|
"importCurlModal.input.label": "cURL Command",
|
||||||
|
"importCurlModal.input.placeholder": "Paste the cURL command here",
|
||||||
|
"ImportCurlModal.notice.content": "This will overwrite any changes you have already made",
|
||||||
|
"importCurlModal.button.label": "Import",
|
||||||
|
"importParameter.label": "Import cURL",
|
||||||
|
"importParameter.showError.invalidCurlCommand.title": "Couldn’t import cURL command",
|
||||||
|
"importParameter.showError.invalidCurlCommand.message": "This command is in an unsupported format",
|
||||||
|
"importParameter.showError.invalidProtocol1.title": "Use the {node} node",
|
||||||
|
"importParameter.showError.invalidProtocol2.title": "Invalid Protocol",
|
||||||
|
"importParameter.showError.invalidProtocol.message": "The HTTP node doesn’t support {protocol} requests"
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load diff
1159
packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts
Normal file
1159
packages/nodes-base/nodes/HttpRequest/V1/HttpRequestV1.node.ts
Normal file
File diff suppressed because it is too large
Load diff
1217
packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts
Normal file
1217
packages/nodes-base/nodes/HttpRequest/V2/HttpRequestV2.node.ts
Normal file
File diff suppressed because it is too large
Load diff
1389
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
Normal file
1389
packages/nodes-base/nodes/HttpRequest/V3/HttpRequestV3.node.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -941,7 +941,8 @@ export type NodePropertyTypes =
|
||||||
| 'options'
|
| 'options'
|
||||||
| 'string'
|
| 'string'
|
||||||
| 'credentialsSelect'
|
| 'credentialsSelect'
|
||||||
| 'resourceLocator';
|
| 'resourceLocator'
|
||||||
|
| 'curlImport';
|
||||||
|
|
||||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue