mirror of
https://github.com/n8n-io/n8n.git
synced 2024-12-24 20:24:05 -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_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": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||
|
@ -9038,6 +9089,11 @@
|
|||
"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": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
@ -9808,8 +9864,7 @@
|
|||
"node_modules/asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||
},
|
||||
"node_modules/asn1": {
|
||||
"version": "0.2.6",
|
||||
|
@ -13788,6 +13843,59 @@
|
|||
"integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==",
|
||||
"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": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||
|
@ -13958,7 +14066,6 @@
|
|||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
|
@ -17037,6 +17144,14 @@
|
|||
"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": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
|
@ -28003,6 +28118,38 @@
|
|||
"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": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz",
|
||||
|
@ -33584,6 +33731,14 @@
|
|||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
|
@ -34265,6 +34420,15 @@
|
|||
"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": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||
|
@ -41556,6 +41720,7 @@
|
|||
"cookie-parser": "^1.4.6",
|
||||
"crypto-js": "~4.1.1",
|
||||
"csrf": "^3.1.0",
|
||||
"curlconverter": "^3.0.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"express-openapi-validator": "^4.13.6",
|
||||
|
@ -44294,6 +44459,47 @@
|
|||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
||||
"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": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz",
|
||||
|
@ -50563,6 +50769,11 @@
|
|||
"integrity": "sha512-HlhVMhrOpBHlkkdSm5wQ6YN65ZqdF9YqP7HS1at6NjQgEOIhBiP7tRZ+dYM3ymztKAX0ovhyKCY9ZnQ/GMK0Qg==",
|
||||
"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": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz",
|
||||
|
@ -51132,8 +51343,7 @@
|
|||
"asap": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
|
||||
"dev": true
|
||||
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
|
||||
},
|
||||
"asn1": {
|
||||
"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": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz",
|
||||
|
@ -54463,8 +54710,7 @@
|
|||
"decode-uri-component": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz",
|
||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==",
|
||||
"dev": true
|
||||
"integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og=="
|
||||
},
|
||||
"dedent": {
|
||||
"version": "0.7.0",
|
||||
|
@ -56777,6 +57023,11 @@
|
|||
"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": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
|
||||
|
@ -64791,6 +65042,7 @@
|
|||
"cookie-parser": "^1.4.6",
|
||||
"crypto-js": "~4.1.1",
|
||||
"csrf": "^3.1.0",
|
||||
"curlconverter": "^3.0.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"express-openapi-validator": "^4.13.6",
|
||||
|
@ -66266,6 +66518,23 @@
|
|||
"integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
|
||||
"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": {
|
||||
"version": "2.2.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
|
||||
|
@ -71205,6 +71479,15 @@
|
|||
"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": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz",
|
||||
|
|
|
@ -124,6 +124,7 @@
|
|||
"cookie-parser": "^1.4.6",
|
||||
"crypto-js": "~4.1.1",
|
||||
"csrf": "^3.1.0",
|
||||
"curlconverter": "^3.0.0",
|
||||
"dotenv": "^8.0.0",
|
||||
"express": "^4.16.4",
|
||||
"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;
|
||||
}
|
||||
|
||||
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
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import clientOAuth1, { RequestOptions } from 'oauth-1.0a';
|
||||
import curlconverter from 'curlconverter';
|
||||
// IMPORTANT! Do not switch to anther bcrypt library unless really necessary and
|
||||
// tested with all possible systems like Windows, Alpine on ARM, FreeBSD, ...
|
||||
import { compare } from 'bcryptjs';
|
||||
|
@ -96,6 +97,7 @@ import { AUTH_COOKIE_NAME, RESPONSE_ERROR_MESSAGES } from './constants';
|
|||
import { credentialsController } from './credentials/credentials.controller';
|
||||
import { oauth2CredentialController } from './credentials/oauth2Credential.api';
|
||||
import type {
|
||||
CurlHelper,
|
||||
ExecutionRequest,
|
||||
NodeListSearchRequest,
|
||||
NodeParameterOptionsRequest,
|
||||
|
@ -151,6 +153,8 @@ import {
|
|||
import glob from 'fast-glob';
|
||||
import { ResponseError } from './ResponseHelper';
|
||||
|
||||
import { toHttpNodeParameters } from './CurlConverterHelper';
|
||||
|
||||
require('body-parser-xml')(bodyParser);
|
||||
|
||||
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
|
||||
// ----------------------------------------
|
||||
|
|
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;
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// /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 {
|
||||
name: 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;
|
||||
oldValue?: string | number;
|
||||
}
|
||||
|
@ -924,6 +924,8 @@ export interface IModalState {
|
|||
open: boolean;
|
||||
mode?: string | null;
|
||||
activeId?: string | null;
|
||||
curlCommand?: string;
|
||||
httpNodeParameters?: string;
|
||||
}
|
||||
|
||||
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 />
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="IMPORT_CURL_MODAL_KEY">
|
||||
<ImportCurlModal />
|
||||
</ModalRoot>
|
||||
|
||||
<ModalRoot :name="COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY">
|
||||
<template v-slot="{ modalName, activeId, mode }">
|
||||
<CommunityPackageManageConfirmModal
|
||||
|
@ -128,6 +132,7 @@ import {
|
|||
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||
WORKFLOW_OPEN_MODAL_KEY,
|
||||
WORKFLOW_SETTINGS_MODAL_KEY,
|
||||
IMPORT_CURL_MODAL_KEY,
|
||||
} from '@/constants';
|
||||
|
||||
import AboutModal from './AboutModal.vue';
|
||||
|
@ -150,6 +155,7 @@ import WorkflowOpen from "./WorkflowOpen.vue";
|
|||
import DeleteUserModal from "./DeleteUserModal.vue";
|
||||
import ExecutionsList from "./ExecutionsList.vue";
|
||||
import ActivationModal from "./ActivationModal.vue";
|
||||
import ImportCurlModal from './ImportCurlModal.vue';
|
||||
|
||||
export default Vue.extend({
|
||||
name: "Modals",
|
||||
|
@ -174,6 +180,7 @@ export default Vue.extend({
|
|||
ValueSurvey,
|
||||
WorkflowSettings,
|
||||
WorkflowOpen,
|
||||
ImportCurlModal,
|
||||
},
|
||||
data: () => ({
|
||||
COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY,
|
||||
|
@ -195,6 +202,7 @@ export default Vue.extend({
|
|||
VALUE_SURVEY_MODAL_KEY,
|
||||
EXECUTIONS_MODAL_KEY,
|
||||
WORKFLOW_ACTIVE_MODAL_KEY,
|
||||
IMPORT_CURL_MODAL_KEY,
|
||||
}),
|
||||
});
|
||||
</script>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -302,6 +302,7 @@ import {
|
|||
|
||||
import CodeEdit from '@/components/CodeEdit.vue';
|
||||
import CredentialsSelect from '@/components/CredentialsSelect.vue';
|
||||
import ImportParameter from '@/components/ImportParameter.vue';
|
||||
import ExpressionEdit from '@/components/ExpressionEdit.vue';
|
||||
import NodeCredentials from '@/components/NodeCredentials.vue';
|
||||
import ScopesNotice from '@/components/ScopesNotice.vue';
|
||||
|
@ -341,6 +342,7 @@ export default mixins(
|
|||
ParameterIssues,
|
||||
ResourceLocator,
|
||||
TextEdit,
|
||||
ImportParameter,
|
||||
},
|
||||
props: [
|
||||
'inputSize',
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<import-parameter
|
||||
v-else-if="parameter.type === 'curlImport' && nodeTypeName === 'n8n-nodes-base.httpRequest' && nodeTypeVersion >= 3"
|
||||
@valueChanged="valueChanged"
|
||||
/>
|
||||
|
||||
<n8n-notice
|
||||
v-else-if="parameter.type === 'notice'"
|
||||
class="parameter-item"
|
||||
|
@ -93,7 +98,6 @@
|
|||
import {
|
||||
INodeParameters,
|
||||
INodeProperties,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
NodeParameterValue,
|
||||
} from 'n8n-workflow';
|
||||
|
@ -104,6 +108,7 @@ import MultipleParameter from '@/components/MultipleParameter.vue';
|
|||
import { genericHelpers } from '@/components/mixins/genericHelpers';
|
||||
import { workflowHelpers } from '@/components/mixins/workflowHelpers';
|
||||
import ParameterInputFull from '@/components/ParameterInputFull.vue';
|
||||
import ImportParameter from '@/components/ImportParameter.vue';
|
||||
|
||||
import { get, set } from 'lodash';
|
||||
|
||||
|
@ -121,6 +126,7 @@ export default mixins(
|
|||
ParameterInputFull,
|
||||
FixedCollectionParameter: () => import('./FixedCollectionParameter.vue') as Promise<Component>,
|
||||
CollectionParameter: () => import('./CollectionParameter.vue') as Promise<Component>,
|
||||
ImportParameter,
|
||||
},
|
||||
props: [
|
||||
'nodeValues', // INodeParameters
|
||||
|
@ -130,6 +136,18 @@ export default mixins(
|
|||
'indent',
|
||||
],
|
||||
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[] {
|
||||
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 COMMUNITY_PACKAGE_INSTALL_MODAL_KEY = 'communityPackageInstall';
|
||||
export const COMMUNITY_PACKAGE_CONFIRM_MODAL_KEY = 'communityPackageManageConfirm';
|
||||
export const IMPORT_CURL_MODAL_KEY = 'importCurl';
|
||||
|
||||
export const COMMUNITY_PACKAGE_MANAGE_ACTIONS = {
|
||||
UNINSTALL: 'uninstall',
|
||||
|
@ -350,3 +351,36 @@ export enum EnterpriseEditionFeature {
|
|||
Sharing = 'sharing',
|
||||
}
|
||||
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 { ITelemetrySettings } from 'n8n-workflow';
|
||||
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> = {
|
||||
namespaced: true,
|
||||
|
@ -183,6 +183,7 @@ const module: Module<ISettingsState, IRootState> = {
|
|||
return e;
|
||||
}
|
||||
},
|
||||
|
||||
async submitValueSurvey(context: ActionContext<ISettingsState, IRootState>, params: IN8nValueSurveyData) {
|
||||
try {
|
||||
const instanceId = context.state.settings.instanceId;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import { getCurlToJson } from '@/api/curlHelper';
|
||||
import { applyForOnboardingCall, fetchNextOnboardingPrompt, submitEmailOnSignup } from '@/api/workflow-webhooks';
|
||||
import {
|
||||
ABOUT_MODAL_KEY,
|
||||
|
@ -22,6 +23,7 @@ import {
|
|||
ONBOARDING_CALL_SIGNUP_MODAL_KEY,
|
||||
FAKE_DOOR_FEATURES,
|
||||
COMMUNITY_PACKAGE_MANAGE_ACTIONS,
|
||||
IMPORT_CURL_MODAL_KEY,
|
||||
} from '@/constants';
|
||||
import Vue from 'vue';
|
||||
import { ActionContext, Module } from 'vuex';
|
||||
|
@ -100,6 +102,11 @@ const module: Module<IUiState, IRootState> = {
|
|||
mode: '',
|
||||
activeId: null,
|
||||
},
|
||||
[IMPORT_CURL_MODAL_KEY]: {
|
||||
open: false,
|
||||
curlCommand: '',
|
||||
httpNodeParameters: '',
|
||||
},
|
||||
},
|
||||
modalStack: [],
|
||||
sidebarMenuCollapsed: true,
|
||||
|
@ -167,6 +174,12 @@ const module: Module<IUiState, IRootState> = {
|
|||
isVersionsOpen: (state: IUiState) => {
|
||||
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) => {
|
||||
return (name: string) => state.modals[name].open;
|
||||
},
|
||||
|
@ -231,6 +244,14 @@ const module: Module<IUiState, IRootState> = {
|
|||
const { name, id } = params;
|
||||
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) => {
|
||||
Vue.set(state.modals[name], 'open', true);
|
||||
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('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}) => {
|
||||
context.commit('setActiveId', { name: CREDENTIAL_EDIT_MODAL_KEY, id: type });
|
||||
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('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.timeoutAfter": "Timeout After",
|
||||
"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'
|
||||
| 'string'
|
||||
| 'credentialsSelect'
|
||||
| 'resourceLocator';
|
||||
| 'resourceLocator'
|
||||
| 'curlImport';
|
||||
|
||||
export type CodeAutocompleteTypes = 'function' | 'functionItem';
|
||||
|
||||
|
|
Loading…
Reference in a new issue