mirror of
https://github.com/usebruno/bruno.git
synced 2025-06-21 20:41:41 +02:00
Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
f781bd7d8a
484
package-lock.json
generated
484
package-lock.json
generated
@ -1826,37 +1826,6 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@codemirror/language": {
|
|
||||||
"version": "0.20.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
|
|
||||||
"integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/state": "^0.20.0",
|
|
||||||
"@codemirror/view": "^0.20.0",
|
|
||||||
"@lezer/common": "^0.16.0",
|
|
||||||
"@lezer/highlight": "^0.16.0",
|
|
||||||
"@lezer/lr": "^0.16.0",
|
|
||||||
"style-mod": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@codemirror/state": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/@codemirror/view": {
|
|
||||||
"version": "0.20.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
|
||||||
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@codemirror/state": "^0.20.0",
|
|
||||||
"style-mod": "^4.0.0",
|
|
||||||
"w3c-keyname": "^2.2.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@develar/schema-utils": {
|
"node_modules/@develar/schema-utils": {
|
||||||
"version": "2.6.5",
|
"version": "2.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||||
@ -2961,30 +2930,6 @@
|
|||||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@lezer/common": {
|
|
||||||
"version": "0.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
|
|
||||||
"integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/@lezer/highlight": {
|
|
||||||
"version": "0.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
|
|
||||||
"integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@lezer/common": "^0.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@lezer/lr": {
|
|
||||||
"version": "0.16.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
|
|
||||||
"integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@lezer/common": "^0.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@malept/cross-spawn-promise": {
|
"node_modules/@malept/cross-spawn-promise": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||||
@ -4839,39 +4784,6 @@
|
|||||||
"node": ">=10.12.0"
|
"node": ">=10.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/autoprefixer": {
|
|
||||||
"version": "10.4.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
|
||||||
"integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/postcss/"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "tidelift",
|
|
||||||
"url": "https://tidelift.com/funding/github/npm/autoprefixer"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
|
||||||
"browserslist": "^4.21.4",
|
|
||||||
"caniuse-lite": "^1.0.30001426",
|
|
||||||
"fraction.js": "^4.2.0",
|
|
||||||
"normalize-range": "^0.1.2",
|
|
||||||
"picocolors": "^1.0.0",
|
|
||||||
"postcss-value-parser": "^4.2.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"autoprefixer": "bin/autoprefixer"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": "^10 || ^12 || >=14"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"postcss": "^8.1.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/aws-sign2": {
|
"node_modules/aws-sign2": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
@ -5300,6 +5212,7 @@
|
|||||||
"version": "4.21.4",
|
"version": "4.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
||||||
"integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
|
"integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -5687,6 +5600,14 @@
|
|||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chai-string": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"chai": "^4.1.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@ -7338,7 +7259,8 @@
|
|||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.284",
|
"version": "1.4.284",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
|
||||||
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
|
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/electron-util": {
|
"node_modules/electron-util": {
|
||||||
"version": "0.17.2",
|
"version": "0.17.2",
|
||||||
@ -8140,19 +8062,6 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fraction.js": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "*"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"type": "patreon",
|
|
||||||
"url": "https://www.patreon.com/infusion"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/fresh": {
|
"node_modules/fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@ -8643,18 +8552,6 @@
|
|||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/graphql-ws": {
|
|
||||||
"version": "5.11.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.2.tgz",
|
|
||||||
"integrity": "sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w==",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=10"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"graphql": ">=0.11 <=16"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/handlebars": {
|
"node_modules/handlebars": {
|
||||||
"version": "4.7.8",
|
"version": "4.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||||
@ -9467,6 +9364,11 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ip": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
||||||
|
},
|
||||||
"node_modules/ipaddr.js": {
|
"node_modules/ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@ -11641,7 +11543,8 @@
|
|||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
|
||||||
"integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A=="
|
"integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/normalize-package-data": {
|
"node_modules/normalize-package-data": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
@ -11678,15 +11581,6 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/normalize-range": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/normalize-url": {
|
"node_modules/normalize-url": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||||
@ -14721,12 +14615,48 @@
|
|||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.0.0",
|
"node": ">= 6.0.0",
|
||||||
"npm": ">= 3.0.0"
|
"npm": ">= 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/socks": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"ip": "^2.0.0",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10.13.0",
|
||||||
|
"npm": ">= 3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks-proxy-agent": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.0.2",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/socks-proxy-agent/node_modules/agent-base": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/source-map": {
|
"node_modules/source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||||
@ -15018,12 +14948,6 @@
|
|||||||
"webpack": "^5.0.0"
|
"webpack": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/style-mod": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/styled-components": {
|
"node_modules/styled-components": {
|
||||||
"version": "5.3.6",
|
"version": "5.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz",
|
||||||
@ -15954,6 +15878,7 @@
|
|||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||||
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
@ -16236,12 +16161,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
||||||
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
||||||
},
|
},
|
||||||
"node_modules/w3c-keyname": {
|
|
||||||
"version": "2.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
|
||||||
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"node_modules/walker": {
|
"node_modules/walker": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||||
@ -16776,11 +16695,11 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-cli": {
|
"packages/bruno-cli": {
|
||||||
"name": "@usebruno/cli",
|
"name": "@usebruno/cli",
|
||||||
"version": "0.11.0",
|
"version": "0.14.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
@ -16788,16 +16707,30 @@
|
|||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
|
"https-proxy-agent": "^7.0.2",
|
||||||
"inquirer": "^9.1.4",
|
"inquirer": "^9.1.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"bru": "bin/bru.js"
|
"bru": "bin/bru.js"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/bruno-cli/node_modules/agent-base": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-cli/node_modules/axios": {
|
"packages/bruno-cli/node_modules/axios": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||||
@ -16820,16 +16753,41 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/bruno-cli/node_modules/http-proxy-agent": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packages/bruno-cli/node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.0.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-electron": {
|
"packages/bruno-electron": {
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"version": "v0.19.0",
|
"version": "v0.22.1",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"@usebruno/schema": "0.5.0",
|
"@usebruno/schema": "0.5.0",
|
||||||
"about-window": "^1.15.2",
|
"about-window": "^1.15.2",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
|
"chai-string": "^1.5.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
@ -16841,6 +16799,7 @@
|
|||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -16848,6 +16807,7 @@
|
|||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vm2": "^3.9.13",
|
"vm2": "^3.9.13",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
@ -16950,6 +16910,18 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/bruno-electron/node_modules/http-proxy-agent": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-electron/node_modules/https-proxy-agent": {
|
"packages/bruno-electron/node_modules/https-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
||||||
@ -17006,7 +16978,7 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-js": {
|
"packages/bruno-js": {
|
||||||
"name": "@usebruno/js",
|
"name": "@usebruno/js",
|
||||||
"version": "0.6.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/query": "0.1.0",
|
"@usebruno/query": "0.1.0",
|
||||||
@ -17058,14 +17030,26 @@
|
|||||||
},
|
},
|
||||||
"packages/bruno-lang": {
|
"packages/bruno-lang": {
|
||||||
"name": "@usebruno/lang",
|
"name": "@usebruno/lang",
|
||||||
"version": "0.5.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arcsecond": "^5.0.0",
|
"arcsecond": "^5.0.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ohm-js": "^16.6.0"
|
"ohm-js": "^16.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/bruno-lang/node_modules/dotenv": {
|
||||||
|
"version": "16.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||||
|
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/bruno-query": {
|
"packages/bruno-query": {
|
||||||
"name": "@usebruno/query",
|
"name": "@usebruno/query",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
@ -18378,37 +18362,6 @@
|
|||||||
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@codemirror/language": {
|
|
||||||
"version": "0.20.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/language/-/language-0.20.2.tgz",
|
|
||||||
"integrity": "sha512-WB3Bnuusw0xhVvhBocieYKwJm04SOk5bPoOEYksVHKHcGHFOaYaw+eZVxR4gIqMMcGzOIUil0FsCmFk8yrhHpw==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
|
||||||
"@codemirror/state": "^0.20.0",
|
|
||||||
"@codemirror/view": "^0.20.0",
|
|
||||||
"@lezer/common": "^0.16.0",
|
|
||||||
"@lezer/highlight": "^0.16.0",
|
|
||||||
"@lezer/lr": "^0.16.0",
|
|
||||||
"style-mod": "^4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@codemirror/state": {
|
|
||||||
"version": "0.20.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/state/-/state-0.20.1.tgz",
|
|
||||||
"integrity": "sha512-ms0tlV5A02OK0pFvTtSUGMLkoarzh1F8mr6jy1cD7ucSC2X/VLHtQCxfhdSEGqTYlQF2hoZtmLv+amqhdgbwjQ==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"@codemirror/view": {
|
|
||||||
"version": "0.20.7",
|
|
||||||
"resolved": "https://registry.npmjs.org/@codemirror/view/-/view-0.20.7.tgz",
|
|
||||||
"integrity": "sha512-pqEPCb9QFTOtHgAH5XU/oVy9UR/Anj6r+tG5CRmkNVcqSKEPmBU05WtN/jxJCFZBXf6HumzWC9ydE4qstO3TxQ==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
|
||||||
"@codemirror/state": "^0.20.0",
|
|
||||||
"style-mod": "^4.0.0",
|
|
||||||
"w3c-keyname": "^2.2.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@develar/schema-utils": {
|
"@develar/schema-utils": {
|
||||||
"version": "2.6.5",
|
"version": "2.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
"resolved": "https://registry.npmjs.org/@develar/schema-utils/-/schema-utils-2.6.5.tgz",
|
||||||
@ -19280,30 +19233,6 @@
|
|||||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@lezer/common": {
|
|
||||||
"version": "0.16.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/common/-/common-0.16.1.tgz",
|
|
||||||
"integrity": "sha512-qPmG7YTZ6lATyTOAWf8vXE+iRrt1NJd4cm2nJHK+v7X9TsOF6+HtuU/ctaZy2RCrluxDb89hI6KWQ5LfQGQWuA==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"@lezer/highlight": {
|
|
||||||
"version": "0.16.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-0.16.0.tgz",
|
|
||||||
"integrity": "sha512-iE5f4flHlJ1g1clOStvXNLbORJoiW4Kytso6ubfYzHnaNo/eo5SKhxs4wv/rtvwZQeZrK3we8S9SyA7OGOoRKQ==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
|
||||||
"@lezer/common": "^0.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@lezer/lr": {
|
|
||||||
"version": "0.16.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-0.16.3.tgz",
|
|
||||||
"integrity": "sha512-pau7um4eAw94BEuuShUIeQDTf3k4Wt6oIUOYxMmkZgDHdqtIcxWND4LRxi8nI9KuT4I1bXQv67BCapkxt7Ywqw==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
|
||||||
"@lezer/common": "^0.16.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"@malept/cross-spawn-promise": {
|
"@malept/cross-spawn-promise": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||||
@ -20102,22 +20031,33 @@
|
|||||||
"@usebruno/cli": {
|
"@usebruno/cli": {
|
||||||
"version": "file:packages/bruno-cli",
|
"version": "file:packages/bruno-cli",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
"decomment": "*",
|
"decomment": "^0.9.5",
|
||||||
"form-data": "^4.0.0",
|
"form-data": "^4.0.0",
|
||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
|
"https-proxy-agent": "^7.0.2",
|
||||||
"inquirer": "^9.1.4",
|
"inquirer": "^9.1.4",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"agent-base": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"axios": {
|
"axios": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz",
|
||||||
@ -20136,6 +20076,24 @@
|
|||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"http-proxy-agent": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"https-proxy-agent": {
|
||||||
|
"version": "7.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
||||||
|
"integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "^7.0.2",
|
||||||
|
"debug": "4"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -20206,8 +20164,16 @@
|
|||||||
"version": "file:packages/bruno-lang",
|
"version": "file:packages/bruno-lang",
|
||||||
"requires": {
|
"requires": {
|
||||||
"arcsecond": "^5.0.0",
|
"arcsecond": "^5.0.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ohm-js": "^16.6.0"
|
"ohm-js": "^16.6.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": {
|
||||||
|
"version": "16.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||||
|
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@usebruno/query": {
|
"@usebruno/query": {
|
||||||
@ -20857,20 +20823,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/atomically/-/atomically-1.7.0.tgz",
|
||||||
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="
|
"integrity": "sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w=="
|
||||||
},
|
},
|
||||||
"autoprefixer": {
|
|
||||||
"version": "10.4.13",
|
|
||||||
"resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.13.tgz",
|
|
||||||
"integrity": "sha512-49vKpMqcZYsJjwotvt4+h/BCjJVnhGwcLpDt5xkcaOG3eLrG/HUYLagrihYsQ+qrIBgIzX1Rw7a6L8I/ZA1Atg==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {
|
|
||||||
"browserslist": "^4.21.4",
|
|
||||||
"caniuse-lite": "^1.0.30001426",
|
|
||||||
"fraction.js": "^4.2.0",
|
|
||||||
"normalize-range": "^0.1.2",
|
|
||||||
"picocolors": "^1.0.0",
|
|
||||||
"postcss-value-parser": "^4.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"aws-sign2": {
|
"aws-sign2": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||||
@ -21200,6 +21152,7 @@
|
|||||||
"version": "4.21.4",
|
"version": "4.21.4",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
|
||||||
"integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
|
"integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"caniuse-lite": "^1.0.30001400",
|
"caniuse-lite": "^1.0.30001400",
|
||||||
"electron-to-chromium": "^1.4.251",
|
"electron-to-chromium": "^1.4.251",
|
||||||
@ -21210,12 +21163,13 @@
|
|||||||
"bruno": {
|
"bruno": {
|
||||||
"version": "file:packages/bruno-electron",
|
"version": "file:packages/bruno-electron",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"@usebruno/schema": "0.5.0",
|
"@usebruno/schema": "0.5.0",
|
||||||
"about-window": "^1.15.2",
|
"about-window": "^1.15.2",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
|
"chai-string": "^1.5.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dmg-license": "^1.0.11",
|
"dmg-license": "^1.0.11",
|
||||||
@ -21231,6 +21185,7 @@
|
|||||||
"fs-extra": "^10.1.0",
|
"fs-extra": "^10.1.0",
|
||||||
"graphql": "^16.6.0",
|
"graphql": "^16.6.0",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
|
"http-proxy-agent": "^7.0.0",
|
||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"is-valid-path": "^0.1.1",
|
"is-valid-path": "^0.1.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -21238,6 +21193,7 @@
|
|||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vm2": "^3.9.13",
|
"vm2": "^3.9.13",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
@ -21300,6 +21256,15 @@
|
|||||||
"pump": "^3.0.0"
|
"pump": "^3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"http-proxy-agent": {
|
||||||
|
"version": "7.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.0.tgz",
|
||||||
|
"integrity": "sha512-+ZT+iBxVUQ1asugqnD6oWoRiS25AkjNfG085dKJGtGxkdwLQrMKU5wJr2bOOFAXzKcTuqq+7fZlTMgG3SRfIYQ==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "^7.1.0",
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"https-proxy-agent": {
|
"https-proxy-agent": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz",
|
||||||
@ -21592,6 +21557,12 @@
|
|||||||
"type-detect": "^4.0.5"
|
"type-detect": "^4.0.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"chai-string": {
|
||||||
|
"version": "1.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz",
|
||||||
|
"integrity": "sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||||
@ -22845,7 +22816,8 @@
|
|||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.4.284",
|
"version": "1.4.284",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
|
||||||
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
|
"integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"electron-util": {
|
"electron-util": {
|
||||||
"version": "0.17.2",
|
"version": "0.17.2",
|
||||||
@ -23470,12 +23442,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||||
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
"integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="
|
||||||
},
|
},
|
||||||
"fraction.js": {
|
|
||||||
"version": "4.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz",
|
|
||||||
"integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"fresh": {
|
"fresh": {
|
||||||
"version": "0.5.2",
|
"version": "0.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
|
||||||
@ -23862,13 +23828,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graphql-ws": {
|
|
||||||
"version": "5.11.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.11.2.tgz",
|
|
||||||
"integrity": "sha512-4EiZ3/UXYcjm+xFGP544/yW1+DVI8ZpKASFbzrV5EDTFWJp0ZvLl4Dy2fSZAzz9imKp5pZMIcjB0x/H69Pv/6w==",
|
|
||||||
"peer": true,
|
|
||||||
"requires": {}
|
|
||||||
},
|
|
||||||
"handlebars": {
|
"handlebars": {
|
||||||
"version": "4.7.8",
|
"version": "4.7.8",
|
||||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||||
@ -24438,6 +24397,11 @@
|
|||||||
"integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==",
|
"integrity": "sha512-xgs2NH9AE66ucSq4cNG1nhSFghr5l6tdL15Pk+jl46bmmBapgoaY/AacXyaDznAqmGL99TiLSQgO/XazFSKYeQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"ip": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ=="
|
||||||
|
},
|
||||||
"ipaddr.js": {
|
"ipaddr.js": {
|
||||||
"version": "1.9.1",
|
"version": "1.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||||
@ -26097,7 +26061,8 @@
|
|||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "2.0.8",
|
"version": "2.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.8.tgz",
|
||||||
"integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A=="
|
"integrity": "sha512-dFSmB8fFHEH/s81Xi+Y/15DQY6VHW81nXRj86EMSL3lmuTmK1e+aT4wrFCkTbm+gSwkw4KpX+rT/pMM2c1mF+A==",
|
||||||
|
"dev": true
|
||||||
},
|
},
|
||||||
"normalize-package-data": {
|
"normalize-package-data": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
@ -26130,12 +26095,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
|
||||||
},
|
},
|
||||||
"normalize-range": {
|
|
||||||
"version": "0.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
|
|
||||||
"integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"normalize-url": {
|
"normalize-url": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
|
||||||
@ -28369,8 +28328,36 @@
|
|||||||
"smart-buffer": {
|
"smart-buffer": {
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
|
||||||
"optional": true
|
},
|
||||||
|
"socks": {
|
||||||
|
"version": "2.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz",
|
||||||
|
"integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==",
|
||||||
|
"requires": {
|
||||||
|
"ip": "^2.0.0",
|
||||||
|
"smart-buffer": "^4.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"socks-proxy-agent": {
|
||||||
|
"version": "8.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.2.tgz",
|
||||||
|
"integrity": "sha512-8zuqoLv1aP/66PHF5TqwJ7Czm3Yv32urJQHrVyhD7mmA6d61Zv8cIXQYPTWwmg6qlupnPvs/QKDmfa4P/qct2g==",
|
||||||
|
"requires": {
|
||||||
|
"agent-base": "^7.0.2",
|
||||||
|
"debug": "^4.3.4",
|
||||||
|
"socks": "^2.7.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "^4.3.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
"version": "0.6.1",
|
"version": "0.6.1",
|
||||||
@ -28601,12 +28588,6 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
"style-mod": {
|
|
||||||
"version": "4.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz",
|
|
||||||
"integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"styled-components": {
|
"styled-components": {
|
||||||
"version": "5.3.6",
|
"version": "5.3.6",
|
||||||
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.6.tgz",
|
||||||
@ -29292,6 +29273,7 @@
|
|||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
|
||||||
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
"integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
|
||||||
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"escalade": "^3.1.1",
|
"escalade": "^3.1.1",
|
||||||
"picocolors": "^1.0.0"
|
"picocolors": "^1.0.0"
|
||||||
@ -29514,12 +29496,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
"resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2.tgz",
|
||||||
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
"integrity": "sha512-zHhCWatviizPIq9B7Vh9uvrH6x3sK8itC84HkamnBWoDFJtzBf7SWlpLCZUit72b3os45h6RWQNC9xHRDF8dRA=="
|
||||||
},
|
},
|
||||||
"w3c-keyname": {
|
|
||||||
"version": "2.2.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz",
|
|
||||||
"integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==",
|
|
||||||
"peer": true
|
|
||||||
},
|
|
||||||
"walker": {
|
"walker": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
|
||||||
|
.auth-mode-selector {
|
||||||
|
background: transparent;
|
||||||
|
|
||||||
|
.auth-mode-label {
|
||||||
|
color: ${(props) => props.theme.colors.text.yellow};
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-item {
|
||||||
|
padding: 0.2rem 0.6rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.caret {
|
||||||
|
color: rgb(140, 140, 140);
|
||||||
|
fill: rgb(140 140 140);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,69 @@
|
|||||||
|
import React, { useRef, forwardRef } from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { IconCaretDown } from '@tabler/icons';
|
||||||
|
import Dropdown from 'components/Dropdown';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { updateCollectionAuthMode } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { humanizeRequestAuthMode } from 'utils/collections';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const AuthMode = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const dropdownTippyRef = useRef();
|
||||||
|
const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref);
|
||||||
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
|
|
||||||
|
const Icon = forwardRef((props, ref) => {
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="flex items-center justify-center auth-mode-label select-none">
|
||||||
|
{humanizeRequestAuthMode(authMode)} <IconCaretDown className="caret ml-1 mr-1" size={14} strokeWidth={2} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const onModeChange = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuthMode({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
mode: value
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper>
|
||||||
|
<div className="inline-flex items-center cursor-pointer auth-mode-selector">
|
||||||
|
<Dropdown onCreate={onDropdownCreate} icon={<Icon />} placement="bottom-end">
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('basic');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Basic Auth
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('bearer');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Bearer Token
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('none');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
No Auth
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default AuthMode;
|
@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,71 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const BasicAuth = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const basicAuth = get(collection, 'root.request.auth.basic', {});
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const handleUsernameChange = (username) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'basic',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: username,
|
||||||
|
password: basicAuth.password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePasswordChange = (password) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'basic',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
username: basicAuth.username,
|
||||||
|
password: password
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Username</label>
|
||||||
|
<div className="single-line-editor-wrapper mb-2">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={basicAuth.username || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleUsernameChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="block font-medium mb-2">Password</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={basicAuth.password || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handlePasswordChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BasicAuth;
|
@ -0,0 +1,16 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
label {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.single-line-editor-wrapper {
|
||||||
|
padding: 0.15rem 0.4rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: solid 1px ${(props) => props.theme.input.border};
|
||||||
|
background-color: ${(props) => props.theme.input.bg};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,46 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { updateCollectionAuth } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const BearerAuth = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const bearerToken = get(collection, 'root.request.auth.bearer.token');
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const handleTokenChange = (token) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionAuth({
|
||||||
|
mode: 'bearer',
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
content: {
|
||||||
|
token: token
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="mt-2 w-full">
|
||||||
|
<label className="block font-medium mb-2">Token</label>
|
||||||
|
<div className="single-line-editor-wrapper">
|
||||||
|
<SingleLineEditor
|
||||||
|
value={bearerToken}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(val) => handleTokenChange(val)}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BearerAuth;
|
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div``;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import AuthMode from './AuthMode';
|
||||||
|
import BearerAuth from './BearerAuth';
|
||||||
|
import BasicAuth from './BasicAuth';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Auth = ({ collection }) => {
|
||||||
|
const authMode = get(collection, 'root.request.auth.mode');
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
const getAuthView = () => {
|
||||||
|
switch (authMode) {
|
||||||
|
case 'basic': {
|
||||||
|
return <BasicAuth collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'bearer': {
|
||||||
|
return <BearerAuth collection={collection} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full mt-2">
|
||||||
|
<div className="flex flex-grow justify-start items-center">
|
||||||
|
<AuthMode collection={collection} />
|
||||||
|
</div>
|
||||||
|
{getAuthView()}
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Auth;
|
@ -0,0 +1,56 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const Wrapper = styled.div`
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-weight: 600;
|
||||||
|
table-layout: fixed;
|
||||||
|
|
||||||
|
thead,
|
||||||
|
td {
|
||||||
|
border: 1px solid ${(props) => props.theme.table.border};
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
color: ${(props) => props.theme.table.thead.color};
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
|
||||||
|
&:nth-child(1) {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(3) {
|
||||||
|
width: 70px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-add-header {
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
outline: none !important;
|
||||||
|
background-color: inherit;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none !important;
|
||||||
|
border: solid 1px transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='checkbox'] {
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default Wrapper;
|
@ -0,0 +1,151 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import { IconTrash } from '@tabler/icons';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import {
|
||||||
|
addCollectionHeader,
|
||||||
|
updateCollectionHeader,
|
||||||
|
deleteCollectionHeader
|
||||||
|
} from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { headers as StandardHTTPHeaders } from 'know-your-http-well';
|
||||||
|
const headerAutoCompleteList = StandardHTTPHeaders.map((e) => e.header);
|
||||||
|
|
||||||
|
const Headers = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
|
||||||
|
const addHeader = () => {
|
||||||
|
dispatch(
|
||||||
|
addCollectionHeader({
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
const handleHeaderValueChange = (e, _header, type) => {
|
||||||
|
const header = cloneDeep(_header);
|
||||||
|
switch (type) {
|
||||||
|
case 'name': {
|
||||||
|
header.name = e.target.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'value': {
|
||||||
|
header.value = e.target.value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'enabled': {
|
||||||
|
header.enabled = e.target.checked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
updateCollectionHeader({
|
||||||
|
header: header,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveHeader = (header) => {
|
||||||
|
dispatch(
|
||||||
|
deleteCollectionHeader({
|
||||||
|
headerUid: header.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>Name</td>
|
||||||
|
<td>Value</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{headers && headers.length
|
||||||
|
? headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<tr key={header.uid}>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.name}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'name'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
autocomplete={headerAutoCompleteList}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<SingleLineEditor
|
||||||
|
value={header.value}
|
||||||
|
theme={storedTheme}
|
||||||
|
onSave={handleSave}
|
||||||
|
onChange={(newValue) =>
|
||||||
|
handleHeaderValueChange(
|
||||||
|
{
|
||||||
|
target: {
|
||||||
|
value: newValue
|
||||||
|
}
|
||||||
|
},
|
||||||
|
header,
|
||||||
|
'value'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
collection={collection}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={header.enabled}
|
||||||
|
tabIndex="-1"
|
||||||
|
className="mr-3 mousetrap"
|
||||||
|
onChange={(e) => handleHeaderValueChange(e, header, 'enabled')}
|
||||||
|
/>
|
||||||
|
<button tabIndex="-1" onClick={() => handleRemoveHeader(header)}>
|
||||||
|
<IconTrash strokeWidth={1.5} size={20} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
: null}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<button className="btn-add-header text-link pr-2 py-3 mt-2 select-none" onClick={addHeader}>
|
||||||
|
+ Add Header
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Headers;
|
@ -19,7 +19,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
enabled: Yup.boolean(),
|
enabled: Yup.boolean(),
|
||||||
protocol: Yup.string().oneOf(['http', 'https']),
|
protocol: Yup.string().oneOf(['http', 'https', 'socks5']),
|
||||||
hostname: Yup.string().max(1024),
|
hostname: Yup.string().max(1024),
|
||||||
port: Yup.number().min(0).max(65535),
|
port: Yup.number().min(0).max(65535),
|
||||||
auth: Yup.object({
|
auth: Yup.object({
|
||||||
@ -49,20 +49,19 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<h1 className="font-medium mb-3">Proxy Settings</h1>
|
|
||||||
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
<form className="bruno-form" onSubmit={formik.handleSubmit}>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="enabled">
|
<label className="settings-label" htmlFor="enabled">
|
||||||
Enabled
|
Enabled
|
||||||
</label>
|
</label>
|
||||||
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
<input type="checkbox" name="enabled" checked={formik.values.enabled} onChange={formik.handleChange} />
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="protocol">
|
<label className="settings-label" htmlFor="protocol">
|
||||||
Protocol
|
Protocol
|
||||||
</label>
|
</label>
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<label className="flex items-center mr-4">
|
<label className="flex items-center">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="protocol"
|
name="protocol"
|
||||||
@ -73,7 +72,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
http
|
http
|
||||||
</label>
|
</label>
|
||||||
<label className="flex items-center">
|
<label className="flex items-center ml-4">
|
||||||
<input
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
name="protocol"
|
name="protocol"
|
||||||
@ -84,9 +83,20 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
https
|
https
|
||||||
</label>
|
</label>
|
||||||
|
<label className="flex items-center ml-4">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="protocol"
|
||||||
|
value="socks5"
|
||||||
|
checked={formik.values.protocol === 'socks5'}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
className="mr-1"
|
||||||
|
/>
|
||||||
|
socks5
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="hostname">
|
<label className="settings-label" htmlFor="hostname">
|
||||||
Hostname
|
Hostname
|
||||||
</label>
|
</label>
|
||||||
@ -106,7 +116,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<div className="text-red-500">{formik.errors.hostname}</div>
|
<div className="text-red-500">{formik.errors.hostname}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="port">
|
<label className="settings-label" htmlFor="port">
|
||||||
Port
|
Port
|
||||||
</label>
|
</label>
|
||||||
@ -124,7 +134,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
|
{formik.touched.port && formik.errors.port ? <div className="text-red-500">{formik.errors.port}</div> : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.enabled">
|
<label className="settings-label" htmlFor="auth.enabled">
|
||||||
Auth
|
Auth
|
||||||
</label>
|
</label>
|
||||||
@ -136,7 +146,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.username">
|
<label className="settings-label" htmlFor="auth.username">
|
||||||
Username
|
Username
|
||||||
</label>
|
</label>
|
||||||
@ -156,7 +166,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
<div className="text-red-500">{formik.errors.auth.username}</div>
|
<div className="text-red-500">{formik.errors.auth.username}</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<div className="ml-4 mb-3 flex items-center">
|
<div className="mb-3 flex items-center">
|
||||||
<label className="settings-label" htmlFor="auth.password">
|
<label className="settings-label" htmlFor="auth.password">
|
||||||
Password
|
Password
|
||||||
</label>
|
</label>
|
||||||
@ -178,7 +188,7 @@ const ProxySettings = ({ proxyConfig, onUpdate }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<button type="submit" className="submit btn btn-md btn-secondary">
|
<button type="submit" className="submit btn btn-sm btn-secondary">
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div`
|
||||||
|
div.CodeMirror {
|
||||||
|
height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.title {
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,73 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import CodeEditor from 'components/CodeEditor';
|
||||||
|
import { updateCollectionRequestScript, updateCollectionResponseScript } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Script = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const requestScript = get(collection, 'root.request.script.req', '');
|
||||||
|
const responseScript = get(collection, 'root.request.script.res', '');
|
||||||
|
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const onRequestScriptEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionRequestScript({
|
||||||
|
script: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResponseScriptEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionResponseScript({
|
||||||
|
script: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full flex flex-col">
|
||||||
|
<div className="flex-1 mt-2">
|
||||||
|
<div className="mb-1 title text-xs">Pre Request</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={requestScript || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onRequestScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1 mt-6">
|
||||||
|
<div className="mt-1 mb-1 title text-xs">Post Response</div>
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={responseScript || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onResponseScriptEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-12">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Script;
|
@ -1,6 +1,32 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
div.tabs {
|
||||||
|
div.tab {
|
||||||
|
padding: 6px 0px;
|
||||||
|
border: none;
|
||||||
|
border-bottom: solid 2px transparent;
|
||||||
|
margin-right: 1.25rem;
|
||||||
|
color: var(--color-tab-inactive);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:active,
|
||||||
|
&:focus-within,
|
||||||
|
&:focus-visible,
|
||||||
|
&:target {
|
||||||
|
outline: none !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: ${(props) => props.theme.tabs.active.color} !important;
|
||||||
|
border-bottom: solid 2px ${(props) => props.theme.tabs.active.border} !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
table {
|
table {
|
||||||
thead,
|
thead,
|
||||||
td {
|
td {
|
||||||
|
@ -0,0 +1,5 @@
|
|||||||
|
import styled from 'styled-components';
|
||||||
|
|
||||||
|
const StyledWrapper = styled.div``;
|
||||||
|
|
||||||
|
export default StyledWrapper;
|
@ -0,0 +1,47 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import CodeEditor from 'components/CodeEditor';
|
||||||
|
import { updateCollectionTests } from 'providers/ReduxStore/slices/collections';
|
||||||
|
import { saveCollectionRoot } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
|
const Tests = ({ collection }) => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const tests = get(collection, 'root.request.tests', '');
|
||||||
|
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
|
||||||
|
const onEdit = (value) => {
|
||||||
|
dispatch(
|
||||||
|
updateCollectionTests({
|
||||||
|
tests: value,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => dispatch(saveCollectionRoot(collection.uid));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="w-full flex flex-col h-full">
|
||||||
|
<CodeEditor
|
||||||
|
collection={collection}
|
||||||
|
value={tests || ''}
|
||||||
|
theme={storedTheme}
|
||||||
|
onEdit={onEdit}
|
||||||
|
mode="javascript"
|
||||||
|
onSave={handleSave}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="mt-6">
|
||||||
|
<button type="submit" className="submit btn btn-sm btn-secondary" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</StyledWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Tests;
|
@ -1,14 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import classnames from 'classnames';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import toast from 'react-hot-toast';
|
import toast from 'react-hot-toast';
|
||||||
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
import { updateBrunoConfig } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { updateSettingsSelectedTab } from 'providers/ReduxStore/slices/collections';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import ProxySettings from './ProxySettings';
|
import ProxySettings from './ProxySettings';
|
||||||
|
import Headers from './Headers';
|
||||||
|
import Auth from './Auth';
|
||||||
|
import Script from './Script';
|
||||||
|
import Test from './Tests';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const CollectionSettings = ({ collection }) => {
|
const CollectionSettings = ({ collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const tab = collection.settingsSelectedTab;
|
||||||
|
const setTab = (tab) => {
|
||||||
|
dispatch(
|
||||||
|
updateSettingsSelectedTab({
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
tab
|
||||||
|
})
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
const proxyConfig = get(collection, 'brunoConfig.proxy', {});
|
||||||
|
|
||||||
@ -22,11 +37,52 @@ const CollectionSettings = ({ collection }) => {
|
|||||||
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
|
.catch((err) => console.log(err) && toast.error('Failed to update collection settings'));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const getTabPanel = (tab) => {
|
||||||
<StyledWrapper className="px-4 py-4">
|
switch (tab) {
|
||||||
<h1 className="font-semibold mb-4">Collection Settings</h1>
|
case 'headers': {
|
||||||
|
return <Headers collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'auth': {
|
||||||
|
return <Auth collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'script': {
|
||||||
|
return <Script collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'tests': {
|
||||||
|
return <Test collection={collection} />;
|
||||||
|
}
|
||||||
|
case 'proxy': {
|
||||||
|
return <ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
<ProxySettings proxyConfig={proxyConfig} onUpdate={onProxySettingsUpdate} />
|
const getTabClassname = (tabName) => {
|
||||||
|
return classnames(`tab select-none ${tabName}`, {
|
||||||
|
active: tabName === tab
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledWrapper className="flex flex-col h-full relative px-4 py-4">
|
||||||
|
<div className="flex flex-wrap items-center tabs" role="tablist">
|
||||||
|
<div className={getTabClassname('headers')} role="tab" onClick={() => setTab('headers')}>
|
||||||
|
Headers
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('auth')} role="tab" onClick={() => setTab('auth')}>
|
||||||
|
Auth
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('script')} role="tab" onClick={() => setTab('script')}>
|
||||||
|
Script
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('tests')} role="tab" onClick={() => setTab('tests')}>
|
||||||
|
Tests
|
||||||
|
</div>
|
||||||
|
<div className={getTabClassname('proxy')} role="tab" onClick={() => setTab('proxy')}>
|
||||||
|
Proxy
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section className={`flex ${['auth', 'script'].includes(tab) ? '' : 'mt-4'}`}>{getTabPanel(tab)}</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -17,7 +17,7 @@ const CopyEnvironment = ({ collection, environment, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -17,7 +17,7 @@ const CreateEnvironment = ({ collection, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -17,7 +17,7 @@ const RenameEnvironment = ({ onClose, environment, collection }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -22,6 +22,7 @@ const Wrapper = styled.div`
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.bruno-modal-card {
|
.bruno-modal-card {
|
||||||
|
@ -61,19 +61,20 @@ const Modal = ({
|
|||||||
children,
|
children,
|
||||||
confirmDisabled,
|
confirmDisabled,
|
||||||
hideCancel,
|
hideCancel,
|
||||||
hideFooter
|
hideFooter,
|
||||||
|
closeModalFadeTimeout = 500
|
||||||
}) => {
|
}) => {
|
||||||
const [isClosing, setIsClosing] = useState(false);
|
const [isClosing, setIsClosing] = useState(false);
|
||||||
const escFunction = (event) => {
|
const escFunction = (event) => {
|
||||||
const escKeyCode = 27;
|
const escKeyCode = 27;
|
||||||
if (event.keyCode === escKeyCode) {
|
if (event.keyCode === escKeyCode) {
|
||||||
closeModal();
|
closeModal({ type: 'esc' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeModal = () => {
|
const closeModal = (args) => {
|
||||||
setIsClosing(true);
|
setIsClosing(true);
|
||||||
setTimeout(() => handleCancel(), 500);
|
setTimeout(() => handleCancel(args), closeModalFadeTimeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -94,12 +95,12 @@ const Modal = ({
|
|||||||
return (
|
return (
|
||||||
<StyledWrapper className={classes}>
|
<StyledWrapper className={classes}>
|
||||||
<div className={`bruno-modal-card modal-${size}`}>
|
<div className={`bruno-modal-card modal-${size}`}>
|
||||||
<ModalHeader title={title} handleCancel={() => closeModal()} />
|
<ModalHeader title={title} handleCancel={() => closeModal({ type: 'icon' })} />
|
||||||
<ModalContent>{children}</ModalContent>
|
<ModalContent>{children}</ModalContent>
|
||||||
<ModalFooter
|
<ModalFooter
|
||||||
confirmText={confirmText}
|
confirmText={confirmText}
|
||||||
cancelText={cancelText}
|
cancelText={cancelText}
|
||||||
handleCancel={() => closeModal()}
|
handleCancel={() => closeModal({ type: 'button' })}
|
||||||
handleSubmit={handleConfirm}
|
handleSubmit={handleConfirm}
|
||||||
confirmDisabled={confirmDisabled}
|
confirmDisabled={confirmDisabled}
|
||||||
hideCancel={hideCancel}
|
hideCancel={hideCancel}
|
||||||
@ -108,7 +109,12 @@ const Modal = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Clicking on backdrop closes the modal */}
|
{/* Clicking on backdrop closes the modal */}
|
||||||
<div className="bruno-modal-backdrop" onClick={() => closeModal()} />
|
<div
|
||||||
|
className="bruno-modal-backdrop"
|
||||||
|
onClick={() => {
|
||||||
|
closeModal({ type: 'backdrop' });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -178,7 +178,7 @@ const AssertionRow = ({
|
|||||||
handleAssertionChange(
|
handleAssertionChange(
|
||||||
{
|
{
|
||||||
target: {
|
target: {
|
||||||
value: newValue
|
value: `${operator} ${newValue}`
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
assertion,
|
assertion,
|
||||||
|
@ -24,7 +24,7 @@ const Auth = ({ item, collection }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="w-full">
|
<StyledWrapper className="w-full mt-1">
|
||||||
<div className="flex flex-grow justify-start items-center">
|
<div className="flex flex-grow justify-start items-center">
|
||||||
<AuthMode item={item} collection={collection} />
|
<AuthMode item={item} collection={collection} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -40,7 +40,7 @@ const GraphQLRequestPane = ({ item, collection, leftPaneWidth, onSchemaLoad, tog
|
|||||||
loadSchema,
|
loadSchema,
|
||||||
isLoading: isSchemaLoading,
|
isLoading: isSchemaLoading,
|
||||||
error: schemaError
|
error: schemaError
|
||||||
} = useGraphqlSchema(url, environment, request, collection.collectionVariables);
|
} = useGraphqlSchema(url, environment, request, collection);
|
||||||
|
|
||||||
const loadGqlSchema = () => {
|
const loadGqlSchema = () => {
|
||||||
if (!isSchemaLoading) {
|
if (!isSchemaLoading) {
|
||||||
|
@ -6,7 +6,7 @@ import { simpleHash } from 'utils/common';
|
|||||||
|
|
||||||
const schemaHashPrefix = 'bruno.graphqlSchema';
|
const schemaHashPrefix = 'bruno.graphqlSchema';
|
||||||
|
|
||||||
const useGraphqlSchema = (endpoint, environment, request, collectionVariables) => {
|
const useGraphqlSchema = (endpoint, environment, request, collection) => {
|
||||||
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
const localStorageKey = `${schemaHashPrefix}.${simpleHash(endpoint)}`;
|
||||||
const [error, setError] = useState(null);
|
const [error, setError] = useState(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
@ -25,7 +25,7 @@ const useGraphqlSchema = (endpoint, environment, request, collectionVariables) =
|
|||||||
|
|
||||||
const loadSchema = () => {
|
const loadSchema = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
fetchGqlSchema(endpoint, environment, request, collectionVariables)
|
fetchGqlSchema(endpoint, environment, request, collection)
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.then((s) => {
|
.then((s) => {
|
||||||
if (s && s.data) {
|
if (s && s.data) {
|
||||||
|
@ -32,6 +32,50 @@ const Wrapper = styled.div`
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tooltip {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip:hover .tooltiptext {
|
||||||
|
visibility: visible;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltiptext {
|
||||||
|
visibility: hidden;
|
||||||
|
width: auto;
|
||||||
|
background-color: ${(props) => props.theme.requestTabs.active.bg};
|
||||||
|
color: ${(props) => props.theme.text};
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
bottom: 34px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltiptext::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 50%;
|
||||||
|
margin-left: -4px;
|
||||||
|
border-width: 4px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: ${(props) => props.theme.requestTabs.active.bg} transparent transparent transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shortcut {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default Wrapper;
|
export default Wrapper;
|
||||||
|
@ -5,8 +5,9 @@ import { requestUrlChanged, updateRequestMethod } from 'providers/ReduxStore/sli
|
|||||||
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
import HttpMethodSelector from './HttpMethodSelector';
|
import HttpMethodSelector from './HttpMethodSelector';
|
||||||
import { useTheme } from 'providers/Theme';
|
import { useTheme } from 'providers/Theme';
|
||||||
import SendIcon from 'components/Icons/Send';
|
import { IconDeviceFloppy, IconArrowRight } from '@tabler/icons';
|
||||||
import SingleLineEditor from 'components/SingleLineEditor';
|
import SingleLineEditor from 'components/SingleLineEditor';
|
||||||
|
import { isMacOS } from 'utils/common/platform';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const QueryUrl = ({ item, collection, handleRun }) => {
|
const QueryUrl = ({ item, collection, handleRun }) => {
|
||||||
@ -14,6 +15,8 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
|
const method = item.draft ? get(item, 'draft.request.method') : get(item, 'request.method');
|
||||||
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
const url = item.draft ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||||
|
const isMac = isMacOS();
|
||||||
|
const saveShortcut = isMac ? 'Cmd + S' : 'Ctrl + S';
|
||||||
|
|
||||||
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
|
const [methodSelectorWidth, setMethodSelectorWidth] = useState(90);
|
||||||
|
|
||||||
@ -22,7 +25,10 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
setMethodSelectorWidth(el.offsetWidth);
|
setMethodSelectorWidth(el.offsetWidth);
|
||||||
}, [method]);
|
}, [method]);
|
||||||
|
|
||||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
const onSave = () => {
|
||||||
|
dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
};
|
||||||
|
|
||||||
const onUrlChange = (value) => {
|
const onUrlChange = (value) => {
|
||||||
dispatch(
|
dispatch(
|
||||||
requestUrlChanged({
|
requestUrlChanged({
|
||||||
@ -65,7 +71,25 @@ const QueryUrl = ({ item, collection, handleRun }) => {
|
|||||||
collection={collection}
|
collection={collection}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
<div className="flex items-center h-full mr-2 cursor-pointer" id="send-request" onClick={handleRun}>
|
||||||
<SendIcon color={theme.requestTabPanel.url.icon} width={22} />
|
<div
|
||||||
|
className="tooltip mr-3"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!item.draft) return;
|
||||||
|
onSave();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<IconDeviceFloppy
|
||||||
|
color={item.draft ? theme.colors.text.yellow : theme.requestTabs.icon.color}
|
||||||
|
strokeWidth={1.5}
|
||||||
|
size={22}
|
||||||
|
className={`${item.draft ? 'cursor-pointer' : 'cursor-default'}`}
|
||||||
|
/>
|
||||||
|
<span className="tooltiptext text-xs">
|
||||||
|
Save <span className="shortcut">({saveShortcut})</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<IconArrowRight color={theme.requestTabPanel.url.icon} strokeWidth={1.5} size={22} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -82,6 +82,15 @@ const RequestBodyMode = ({ item, collection }) => {
|
|||||||
>
|
>
|
||||||
TEXT
|
TEXT
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={() => {
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
onModeChange('sparql');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
SPARQL
|
||||||
|
</div>
|
||||||
<div className="label-item font-medium">Other</div>
|
<div className="label-item font-medium">Other</div>
|
||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
|
@ -28,17 +28,19 @@ const RequestBody = ({ item, collection }) => {
|
|||||||
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
const onRun = () => dispatch(sendRequest(item, collection.uid));
|
||||||
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
const onSave = () => dispatch(saveRequest(item.uid, collection.uid));
|
||||||
|
|
||||||
if (['json', 'xml', 'text'].includes(bodyMode)) {
|
if (['json', 'xml', 'text', 'sparql'].includes(bodyMode)) {
|
||||||
let codeMirrorMode = {
|
let codeMirrorMode = {
|
||||||
json: 'application/ld+json',
|
json: 'application/ld+json',
|
||||||
text: 'application/text',
|
text: 'application/text',
|
||||||
xml: 'application/xml'
|
xml: 'application/xml',
|
||||||
|
sparql: 'application/sparql-query'
|
||||||
};
|
};
|
||||||
|
|
||||||
let bodyContent = {
|
let bodyContent = {
|
||||||
json: body.json,
|
json: body.json,
|
||||||
text: body.text,
|
text: body.text,
|
||||||
xml: body.xml
|
xml: body.xml,
|
||||||
|
sparql: body.sparql
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import Modal from 'components/Modal';
|
||||||
|
|
||||||
|
const ConfirmRequestClose = ({ onCancel, onCloseWithoutSave, onSaveAndClose }) => {
|
||||||
|
const _handleCancel = ({ type }) => {
|
||||||
|
if (type === 'button') {
|
||||||
|
return onCloseWithoutSave();
|
||||||
|
}
|
||||||
|
|
||||||
|
return onCancel();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
size="sm"
|
||||||
|
title="Unsaved changes"
|
||||||
|
confirmText="Save and Close"
|
||||||
|
cancelText="Close without saving"
|
||||||
|
handleConfirm={onSaveAndClose}
|
||||||
|
handleCancel={_handleCancel}
|
||||||
|
disableEscapeKey={true}
|
||||||
|
disableCloseOnOutsideClick={true}
|
||||||
|
closeModalFadeTimeout={150}
|
||||||
|
>
|
||||||
|
<div className="font-normal">You have unsaved changes in you request.</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmRequestClose;
|
@ -8,7 +8,7 @@ const SpecialTab = ({ handleCloseClick, type }) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
<IconSettings size={18} strokeWidth={1.5} className="text-yellow-600" />
|
||||||
<span className="ml-1">Settings</span>
|
<span className="ml-1">Collection</span>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,22 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
|
import { saveRequest } from 'providers/ReduxStore/slices/collections/actions';
|
||||||
|
import { deleteRequestDraft } from 'providers/ReduxStore/slices/collections';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
import { findItemInCollection } from 'utils/collections';
|
import { findItemInCollection } from 'utils/collections';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
import RequestTabNotFound from './RequestTabNotFound';
|
import RequestTabNotFound from './RequestTabNotFound';
|
||||||
|
import ConfirmRequestClose from './ConfirmRequestClose';
|
||||||
import SpecialTab from './SpecialTab';
|
import SpecialTab from './SpecialTab';
|
||||||
|
import { useTheme } from 'providers/Theme';
|
||||||
|
import darkTheme from 'themes/dark';
|
||||||
|
import lightTheme from 'themes/light';
|
||||||
|
|
||||||
const RequestTab = ({ tab, collection }) => {
|
const RequestTab = ({ tab, collection }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { storedTheme } = useTheme();
|
||||||
|
const [showConfirmClose, setShowConfirmClose] = useState(false);
|
||||||
|
|
||||||
const handleCloseClick = (event) => {
|
const handleCloseClick = (event) => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
@ -21,35 +29,38 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getMethodColor = (method = '') => {
|
const getMethodColor = (method = '') => {
|
||||||
|
const theme = storedTheme === 'dark' ? darkTheme : lightTheme;
|
||||||
|
|
||||||
let color = '';
|
let color = '';
|
||||||
method = method.toLocaleLowerCase();
|
method = method.toLocaleLowerCase();
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case 'get': {
|
case 'get': {
|
||||||
color = 'var(--color-method-get)';
|
color = theme.request.methods.get;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'post': {
|
case 'post': {
|
||||||
color = 'var(--color-method-post)';
|
color = theme.request.methods.post;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'put': {
|
case 'put': {
|
||||||
color = 'var(--color-method-put)';
|
color = theme.request.methods.put;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'delete': {
|
case 'delete': {
|
||||||
color = 'var(--color-method-delete)';
|
color = theme.request.methods.delete;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'patch': {
|
case 'patch': {
|
||||||
color = 'var(--color-method-patch)';
|
color = theme.request.methods.patch;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'options': {
|
case 'options': {
|
||||||
color = 'var(--color-method-options)';
|
color = theme.request.methods.options;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'head': {
|
case 'head': {
|
||||||
color = 'var(--color-method-head)';
|
color = theme.request.methods.head;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,6 +90,39 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
<StyledWrapper className="flex items-center justify-between tab-container px-1">
|
||||||
|
{showConfirmClose && (
|
||||||
|
<ConfirmRequestClose
|
||||||
|
onCancel={() => setShowConfirmClose(false)}
|
||||||
|
onCloseWithoutSave={() => {
|
||||||
|
dispatch(
|
||||||
|
deleteRequestDraft({
|
||||||
|
itemUid: item.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
dispatch(
|
||||||
|
closeTabs({
|
||||||
|
tabUids: [tab.uid]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setShowConfirmClose(false);
|
||||||
|
}}
|
||||||
|
onSaveAndClose={() => {
|
||||||
|
dispatch(saveRequest(item.uid, collection.uid))
|
||||||
|
.then(() => {
|
||||||
|
dispatch(
|
||||||
|
closeTabs({
|
||||||
|
tabUids: [tab.uid]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setShowConfirmClose(false);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log('err', err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<div className="flex items-baseline tab-label pl-2">
|
<div className="flex items-baseline tab-label pl-2">
|
||||||
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
|
<span className="tab-method uppercase" style={{ color: getMethodColor(method), fontSize: 12 }}>
|
||||||
{method}
|
{method}
|
||||||
@ -87,7 +131,14 @@ const RequestTab = ({ tab, collection }) => {
|
|||||||
{item.name}
|
{item.name}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex px-2 close-icon-container" onClick={(e) => handleCloseClick(e)}>
|
<div
|
||||||
|
className="flex px-2 close-icon-container"
|
||||||
|
onClick={(e) => {
|
||||||
|
if (!item.draft) return handleCloseClick(e);
|
||||||
|
|
||||||
|
setShowConfirmClose(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{!item.draft ? (
|
{!item.draft ? (
|
||||||
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
<svg focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512" className="close-icon">
|
||||||
<path
|
<path
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
|
||||||
const StyledWrapper = styled.div`
|
const StyledWrapper = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1;
|
||||||
|
background-color: ${(props) => props.theme.requestTabPanel.responseOverlayBg};
|
||||||
|
|
||||||
div.overlay {
|
div.overlay {
|
||||||
position: absolute;
|
height: 100%;
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 9;
|
z-index: 9;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -14,6 +15,11 @@ const StyledWrapper = styled.div`
|
|||||||
padding-top: 20%;
|
padding-top: 20%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
.loading-icon {
|
||||||
|
transform: scaleY(-1);
|
||||||
|
animation: rotateCounterClockwise 1s linear infinite;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -13,17 +13,17 @@ const ResponseLoadingOverlay = ({ item, collection }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="mt-4 px-3 w-full">
|
<StyledWrapper className="px-3 w-full">
|
||||||
<div className="overlay">
|
<div className="overlay">
|
||||||
<div style={{ marginBottom: 15, fontSize: 26 }}>
|
<div style={{ marginBottom: 15, fontSize: 26 }}>
|
||||||
<div style={{ display: 'inline-block', fontSize: 24, marginLeft: 5, marginRight: 5 }}>
|
<div style={{ display: 'inline-block', fontSize: 20, marginLeft: 5, marginRight: 5 }}>
|
||||||
<StopWatch />
|
<StopWatch />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<IconRefresh size={24} className="animate-spin" />
|
<IconRefresh size={24} className="loading-icon" />
|
||||||
<button
|
<button
|
||||||
onClick={handleCancelRequest}
|
onClick={handleCancelRequest}
|
||||||
className="mt-4 uppercase btn-md rounded btn-secondary ease-linear transition-all duration-150"
|
className="mt-4 uppercase btn-sm rounded btn-secondary ease-linear transition-all duration-150"
|
||||||
type="button"
|
type="button"
|
||||||
>
|
>
|
||||||
Cancel Request
|
Cancel Request
|
||||||
|
@ -1,8 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { IconSend } from '@tabler/icons';
|
import { IconSend } from '@tabler/icons';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
import { isMacOS } from 'utils/common/platform';
|
||||||
|
|
||||||
const Placeholder = () => {
|
const Placeholder = () => {
|
||||||
|
const isMac = isMacOS();
|
||||||
|
const sendRequestShortcut = isMac ? 'Cmd + Enter' : 'Ctrl + Enter';
|
||||||
|
const newRequestShortcut = isMac ? 'Cmd + B' : 'Ctrl + B';
|
||||||
|
const editEnvironmentShortcut = isMac ? 'Cmd + E' : 'Ctrl + E';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper>
|
<StyledWrapper>
|
||||||
<div className="send-icon flex justify-center" style={{ fontSize: 200 }}>
|
<div className="send-icon flex justify-center" style={{ fontSize: 200 }}>
|
||||||
@ -15,9 +21,9 @@ const Placeholder = () => {
|
|||||||
<div className="px-1 py-2">Edit Environments</div>
|
<div className="px-1 py-2">Edit Environments</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-1 flex-col px-1">
|
<div className="flex flex-1 flex-col px-1">
|
||||||
<div className="px-1 py-2">Cmd + Enter</div>
|
<div className="px-1 py-2">{sendRequestShortcut}</div>
|
||||||
<div className="px-1 py-2">Cmd + B</div>
|
<div className="px-1 py-2">{newRequestShortcut}</div>
|
||||||
<div className="px-1 py-2">Cmd + E</div>
|
<div className="px-1 py-2">{editEnvironmentShortcut}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
|
@ -45,6 +45,10 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h
|
|||||||
return safeStringifyJSON(data);
|
return safeStringifyJSON(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mode.includes('image')) {
|
||||||
|
return item.requestSent.url;
|
||||||
|
}
|
||||||
|
|
||||||
// final fallback
|
// final fallback
|
||||||
if (typeof data === 'string') {
|
if (typeof data === 'string') {
|
||||||
return data;
|
return data;
|
||||||
@ -87,7 +91,13 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h
|
|||||||
};
|
};
|
||||||
|
|
||||||
const activeResult = useMemo(() => {
|
const activeResult = useMemo(() => {
|
||||||
if (tab === 'preview' && mode.includes('html')) {
|
if (
|
||||||
|
tab === 'preview' &&
|
||||||
|
mode.includes('html') &&
|
||||||
|
item.requestSent &&
|
||||||
|
item.requestSent.url &&
|
||||||
|
typeof data === 'string'
|
||||||
|
) {
|
||||||
// Add the Base tag to the head so content loads properly. This also needs the correct CSP settings
|
// Add the Base tag to the head so content loads properly. This also needs the correct CSP settings
|
||||||
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent.url}">`);
|
const webViewSrc = data.replace('<head>', `<head><base href="${item.requestSent.url}">`);
|
||||||
return (
|
return (
|
||||||
@ -97,6 +107,8 @@ const QueryResult = ({ item, collection, data, width, disableRunEventListener, h
|
|||||||
className="h-full bg-white"
|
className="h-full bg-white"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
} else if (mode.includes('image')) {
|
||||||
|
return <img src={item.requestSent.url} alt="image" />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value} mode={mode} readOnly />;
|
return <CodeEditor collection={collection} theme={storedTheme} onRun={onRun} value={value} mode={mode} readOnly />;
|
||||||
|
@ -16,7 +16,7 @@ const TestResults = ({ results, assertionResults }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex flex-col px-3">
|
<StyledWrapper className="flex flex-col px-3">
|
||||||
<div className="py-2 font-medium test-summary">
|
<div className="pb-2 font-medium test-summary">
|
||||||
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
|
Tests ({results.length}/{results.length}), Passed: {passedTests.length}, Failed: {failedTests.length}
|
||||||
</div>
|
</div>
|
||||||
<ul className="">
|
<ul className="">
|
||||||
|
@ -61,15 +61,15 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading && !item.response) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex h-full relative">
|
<StyledWrapper className="flex flex-col h-full relative">
|
||||||
<Overlay item={item} collection={collection} />
|
<Overlay item={item} collection={collection} />
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.state !== 'success') {
|
if (!item.response) {
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="flex h-full relative">
|
<StyledWrapper className="flex h-full relative">
|
||||||
<Placeholder />
|
<Placeholder />
|
||||||
@ -115,7 +115,10 @@ const ResponsePane = ({ rightPaneWidth, item, collection }) => {
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
<section className="flex flex-grow">{getTabPanel(focusedTab.responsePaneTab)}</section>
|
<section className={`flex flex-grow relative ${focusedTab.responsePaneTab === 'response' ? '' : 'mt-4'}`}>
|
||||||
|
{isLoading ? <Overlay item={item} collection={collection} /> : null}
|
||||||
|
{getTabPanel(focusedTab.responsePaneTab)}
|
||||||
|
</section>
|
||||||
</StyledWrapper>
|
</StyledWrapper>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -18,7 +18,7 @@ const CloneCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -9,7 +9,7 @@ import { findEnvironmentInCollection } from 'utils/collections';
|
|||||||
|
|
||||||
const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) => {
|
const interpolateUrl = ({ url, envVars, collectionVariables, processEnvVars }) => {
|
||||||
if (!url || !url.length || typeof url !== 'string') {
|
if (!url || !url.length || typeof url !== 'string') {
|
||||||
return str;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const template = handlebars.compile(url, { noEscape: true });
|
const template = handlebars.compile(url, { noEscape: true });
|
||||||
@ -74,9 +74,8 @@ const languages = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
const GenerateCodeItem = ({ collection, item, onClose }) => {
|
||||||
const url = get(item, 'request.url') || '';
|
const url = get(item, 'draft.request.url') !== undefined ? get(item, 'draft.request.url') : get(item, 'request.url');
|
||||||
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
const environment = findEnvironmentInCollection(collection, collection.activeEnvironmentUid);
|
||||||
|
|
||||||
let envVars = {};
|
let envVars = {};
|
||||||
if (environment) {
|
if (environment) {
|
||||||
const vars = get(environment, 'variables', []);
|
const vars = get(environment, 'variables', []);
|
||||||
@ -92,7 +91,6 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
collectionVariables: collection.collectionVariables,
|
collectionVariables: collection.collectionVariables,
|
||||||
processEnvVars: collection.processEnvVariables
|
processEnvVars: collection.processEnvVariables
|
||||||
});
|
});
|
||||||
|
|
||||||
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
|
const [selectedLanguage, setSelectedLanguage] = useState(languages[0]);
|
||||||
return (
|
return (
|
||||||
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
|
<Modal size="lg" title="Generate Code" handleCancel={onClose} hideFooter={true}>
|
||||||
@ -121,10 +119,16 @@ const GenerateCodeItem = ({ collection, item, onClose }) => {
|
|||||||
language={selectedLanguage}
|
language={selectedLanguage}
|
||||||
item={{
|
item={{
|
||||||
...item,
|
...item,
|
||||||
request: {
|
request:
|
||||||
...item.request,
|
item.request.url !== ''
|
||||||
url: interpolatedUrl
|
? {
|
||||||
}
|
...item.request,
|
||||||
|
url: interpolatedUrl
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
...item.draft.request,
|
||||||
|
url: interpolatedUrl
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
|
@ -17,7 +17,7 @@ const RenameCollectionItem = ({ collection, item, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -25,13 +25,13 @@ const Wrapper = styled.div`
|
|||||||
color: ${(props) => props.theme.request.methods.delete};
|
color: ${(props) => props.theme.request.methods.delete};
|
||||||
}
|
}
|
||||||
.method-patch {
|
.method-patch {
|
||||||
color: ${(props) => props.theme.request.methods.put};
|
color: ${(props) => props.theme.request.methods.patch};
|
||||||
}
|
}
|
||||||
.method-options {
|
.method-options {
|
||||||
color: ${(props) => props.theme.request.methods.put};
|
color: ${(props) => props.theme.request.methods.options};
|
||||||
}
|
}
|
||||||
.method-head {
|
.method-head {
|
||||||
color: ${(props) => props.theme.request.methods.put};
|
color: ${(props) => props.theme.request.methods.head};
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -15,7 +15,8 @@ const RequestMethod = ({ item }) => {
|
|||||||
'method-put': method === 'put',
|
'method-put': method === 'put',
|
||||||
'method-delete': method === 'delete',
|
'method-delete': method === 'delete',
|
||||||
'method-patch': method === 'patch',
|
'method-patch': method === 'patch',
|
||||||
'method-head': method === 'head'
|
'method-head': method === 'head',
|
||||||
|
'method-options': method == 'options'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import { isItemARequest, isItemAFolder, itemIsOpenedInTabs } from 'utils/tabs';
|
|||||||
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
|
import { doesRequestMatchSearchText, doesFolderHaveItemsMatchSearchText } from 'utils/collections/search';
|
||||||
import { getDefaultRequestPaneTab } from 'utils/collections';
|
import { getDefaultRequestPaneTab } from 'utils/collections';
|
||||||
import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
import { hideHomePage } from 'providers/ReduxStore/slices/app';
|
||||||
|
import toast from 'react-hot-toast';
|
||||||
import StyledWrapper from './StyledWrapper';
|
import StyledWrapper from './StyledWrapper';
|
||||||
|
|
||||||
const CollectionItem = ({ item, collection, searchText }) => {
|
const CollectionItem = ({ item, collection, searchText }) => {
|
||||||
@ -88,30 +88,44 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
if (isItemARequest(item)) {
|
switch (event.button) {
|
||||||
if (itemIsOpenedInTabs(item, tabs)) {
|
case 0: // left click
|
||||||
|
if (isItemARequest(item)) {
|
||||||
|
dispatch(hideHomePage());
|
||||||
|
if (itemIsOpenedInTabs(item, tabs)) {
|
||||||
|
dispatch(
|
||||||
|
focusTab({
|
||||||
|
uid: item.uid
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dispatch(
|
||||||
|
addTab({
|
||||||
|
uid: item.uid,
|
||||||
|
collectionUid: collection.uid,
|
||||||
|
requestPaneTab: getDefaultRequestPaneTab(item)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
dispatch(
|
dispatch(
|
||||||
focusTab({
|
collectionFolderClicked({
|
||||||
uid: item.uid
|
itemUid: item.uid,
|
||||||
|
collectionUid: collection.uid
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
return;
|
||||||
dispatch(
|
case 2: // right click
|
||||||
addTab({
|
const _menuDropdown = dropdownTippyRef.current;
|
||||||
uid: item.uid,
|
if (_menuDropdown) {
|
||||||
collectionUid: collection.uid,
|
let menuDropdownBehavior = 'show';
|
||||||
requestPaneTab: getDefaultRequestPaneTab(item)
|
if (_menuDropdown.state.isShown) {
|
||||||
})
|
menuDropdownBehavior = 'hide';
|
||||||
);
|
}
|
||||||
}
|
_menuDropdown[menuDropdownBehavior]();
|
||||||
dispatch(hideHomePage());
|
}
|
||||||
} else {
|
return;
|
||||||
dispatch(
|
|
||||||
collectionFolderClicked({
|
|
||||||
itemUid: item.uid,
|
|
||||||
collectionUid: collection.uid
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -148,7 +162,15 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
const sortFolderItems = (items = []) => {
|
const sortFolderItems = (items = []) => {
|
||||||
return items.sort((a, b) => a.name.localeCompare(b.name));
|
return items.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
};
|
};
|
||||||
|
const handleGenerateCode = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
dropdownTippyRef.current.hide();
|
||||||
|
if (item.request.url !== '' || (item.draft?.request.url !== undefined && item.draft?.request.url !== '')) {
|
||||||
|
setGenerateCodeItemModalOpen(true);
|
||||||
|
} else {
|
||||||
|
toast.error('URL is required');
|
||||||
|
}
|
||||||
|
};
|
||||||
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
const requestItems = sortRequestItems(filter(item.items, (i) => isItemARequest(i)));
|
||||||
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
const folderItems = sortFolderItems(filter(item.items, (i) => isItemAFolder(i)));
|
||||||
|
|
||||||
@ -181,7 +203,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
? indents.map((i) => {
|
? indents.map((i) => {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onClick={handleClick}
|
onMouseUp={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
className="indent-block"
|
className="indent-block"
|
||||||
key={i}
|
key={i}
|
||||||
@ -197,7 +219,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
})
|
})
|
||||||
: null}
|
: null}
|
||||||
<div
|
<div
|
||||||
onClick={handleClick}
|
onMouseUp={handleClick}
|
||||||
onDoubleClick={handleDoubleClick}
|
onDoubleClick={handleDoubleClick}
|
||||||
className="flex flex-grow items-center h-full overflow-hidden"
|
className="flex flex-grow items-center h-full overflow-hidden"
|
||||||
style={{
|
style={{
|
||||||
@ -279,9 +301,7 @@ const CollectionItem = ({ item, collection, searchText }) => {
|
|||||||
<div
|
<div
|
||||||
className="dropdown-item"
|
className="dropdown-item"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
handleGenerateCode(e);
|
||||||
dropdownTippyRef.current.hide();
|
|
||||||
setGenerateCodeItemModalOpen(true);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Generate Code
|
Generate Code
|
||||||
|
@ -16,7 +16,7 @@ const RenameCollection = ({ collection, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
name: Yup.string()
|
name: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -64,7 +64,21 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleClick = (event) => {
|
const handleClick = (event) => {
|
||||||
dispatch(collectionClicked(collection.uid));
|
const _menuDropdown = menuDropdownTippyRef.current;
|
||||||
|
switch (event.button) {
|
||||||
|
case 0: // left click
|
||||||
|
dispatch(collectionClicked(collection.uid));
|
||||||
|
return;
|
||||||
|
case 2: // right click
|
||||||
|
if (_menuDropdown) {
|
||||||
|
let menuDropdownBehavior = 'show';
|
||||||
|
if (_menuDropdown.state.isShown) {
|
||||||
|
menuDropdownBehavior = 'hide';
|
||||||
|
}
|
||||||
|
_menuDropdown[menuDropdownBehavior]();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleExportClick = () => {
|
const handleExportClick = () => {
|
||||||
@ -119,7 +133,7 @@ const Collection = ({ collection, searchText }) => {
|
|||||||
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
<CollectionProperties collection={collection} onClose={() => setCollectionPropertiesModal(false)} />
|
||||||
)}
|
)}
|
||||||
<div className="flex py-1 collection-name items-center" ref={drop}>
|
<div className="flex py-1 collection-name items-center" ref={drop}>
|
||||||
<div className="flex flex-grow items-center overflow-hidden" onClick={handleClick}>
|
<div className="flex flex-grow items-center overflow-hidden" onMouseUp={handleClick}>
|
||||||
<IconChevronRight
|
<IconChevronRight
|
||||||
size={16}
|
size={16}
|
||||||
strokeWidth={2}
|
strokeWidth={2}
|
||||||
|
@ -21,11 +21,11 @@ const CreateCollection = ({ onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
collectionName: Yup.string()
|
collectionName: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.required('collection name is required'),
|
.required('collection name is required'),
|
||||||
collectionFolderName: Yup.string()
|
collectionFolderName: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(50, 'must be 50 characters or less')
|
.max(50, 'must be 50 characters or less')
|
||||||
.matches(/^[\w\-. ]+$/, 'Folder name contains invalid characters')
|
.matches(/^[\w\-. ]+$/, 'Folder name contains invalid characters')
|
||||||
.required('folder name is required'),
|
.required('folder name is required'),
|
||||||
|
@ -16,7 +16,7 @@ const ImportCollectionLocation = ({ onClose, handleSubmit, collectionName }) =>
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
collectionLocation: Yup.string()
|
collectionLocation: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.min(1, 'must be at least 1 character')
|
||||||
.max(500, 'must be 500 characters or less')
|
.max(500, 'must be 500 characters or less')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
}),
|
}),
|
||||||
|
@ -16,7 +16,8 @@ const NewFolder = ({ collection, item, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
folderName: Yup.string()
|
folderName: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.trim()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
.test({
|
.test({
|
||||||
name: 'folderName',
|
name: 'folderName',
|
||||||
@ -32,7 +33,7 @@ const NewFolder = ({ collection, item, onClose }) => {
|
|||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null))
|
dispatch(newFolder(values.folderName, collection.uid, item ? item.uid : null))
|
||||||
.then(() => onClose())
|
.then(() => onClose())
|
||||||
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the request'));
|
.catch((err) => toast.error(err ? err.message : 'An error occurred while adding the folder'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,12 +25,16 @@ const NewRequest = ({ collection, item, isEphemeral, onClose }) => {
|
|||||||
},
|
},
|
||||||
validationSchema: Yup.object({
|
validationSchema: Yup.object({
|
||||||
requestName: Yup.string()
|
requestName: Yup.string()
|
||||||
.min(1, 'must be atleast 1 characters')
|
.trim()
|
||||||
|
.min(1, 'must be at least 1 character')
|
||||||
.required('name is required')
|
.required('name is required')
|
||||||
.test({
|
.test({
|
||||||
name: 'requestName',
|
name: 'requestName',
|
||||||
message: 'The request name "index" is reserved in bruno',
|
message: `The request names - collection and folder is reserved in bruno`,
|
||||||
test: (value) => value && !value.trim().toLowerCase().includes('index')
|
test: (value) => {
|
||||||
|
const trimmedValue = value ? value.trim().toLowerCase() : '';
|
||||||
|
return !['collection', 'folder'].includes(trimmedValue);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
onSubmit: (values) => {
|
onSubmit: (values) => {
|
||||||
|
@ -18,6 +18,7 @@ const TitleBar = () => {
|
|||||||
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
|
const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false);
|
||||||
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
|
const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
const handleImportCollection = (collection) => {
|
const handleImportCollection = (collection) => {
|
||||||
setImportedCollection(collection);
|
setImportedCollection(collection);
|
||||||
@ -50,6 +51,10 @@ const TitleBar = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const openDevTools = () => {
|
||||||
|
ipcRenderer.invoke('renderer:open-devtools');
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledWrapper className="px-2 py-2">
|
<StyledWrapper className="px-2 py-2">
|
||||||
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
|
{createCollectionModalOpen ? <CreateCollection onClose={() => setCreateCollectionModalOpen(false)} /> : null}
|
||||||
@ -104,6 +109,15 @@ const TitleBar = () => {
|
|||||||
>
|
>
|
||||||
Import Collection
|
Import Collection
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
className="dropdown-item"
|
||||||
|
onClick={(e) => {
|
||||||
|
menuDropdownTippyRef.current.hide();
|
||||||
|
openDevTools();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Devtools
|
||||||
|
</div>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -105,7 +105,7 @@ const Sidebar = () => {
|
|||||||
Star
|
Star
|
||||||
</GitHubButton>
|
</GitHubButton>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.20.0</div>
|
<div className="flex flex-grow items-center justify-end text-xs mr-2">v0.23.0</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -129,6 +129,24 @@ const GlobalStyle = createGlobalStyle`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes rotateClockwise {
|
||||||
|
0% {
|
||||||
|
transform: scaleY(-1) rotate(0deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(-1) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotateCounterClockwise {
|
||||||
|
0% {
|
||||||
|
transform: scaleY(-1) rotate(360deg);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scaleY(-1) rotate(0deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// codemirror
|
// codemirror
|
||||||
.CodeMirror {
|
.CodeMirror {
|
||||||
.cm-variable-valid {
|
.cm-variable-valid {
|
||||||
|
@ -14,24 +14,26 @@ const SERVER_RENDERED = typeof navigator === 'undefined' || global['PREVENT_CODE
|
|||||||
if (!SERVER_RENDERED) {
|
if (!SERVER_RENDERED) {
|
||||||
require('codemirror/mode/javascript/javascript');
|
require('codemirror/mode/javascript/javascript');
|
||||||
require('codemirror/mode/xml/xml');
|
require('codemirror/mode/xml/xml');
|
||||||
require('codemirror/addon/scroll/simplescrollbars');
|
require('codemirror/mode/sparql/sparql');
|
||||||
|
require('codemirror/addon/comment/comment');
|
||||||
|
require('codemirror/addon/dialog/dialog');
|
||||||
|
require('codemirror/addon/edit/closebrackets');
|
||||||
require('codemirror/addon/edit/matchbrackets');
|
require('codemirror/addon/edit/matchbrackets');
|
||||||
require('codemirror/addon/fold/brace-fold');
|
require('codemirror/addon/fold/brace-fold');
|
||||||
require('codemirror/addon/fold/foldgutter');
|
require('codemirror/addon/fold/foldgutter');
|
||||||
require('codemirror/addon/mode/overlay');
|
|
||||||
require('codemirror/addon/hint/show-hint');
|
require('codemirror/addon/hint/show-hint');
|
||||||
require('codemirror/keymap/sublime');
|
require('codemirror/addon/lint/lint');
|
||||||
require('codemirror/addon/comment/comment');
|
require('codemirror/addon/mode/overlay');
|
||||||
require('codemirror/addon/edit/closebrackets');
|
require('codemirror/addon/scroll/simplescrollbars');
|
||||||
|
require('codemirror/addon/search/jump-to-line');
|
||||||
require('codemirror/addon/search/search');
|
require('codemirror/addon/search/search');
|
||||||
require('codemirror/addon/search/searchcursor');
|
require('codemirror/addon/search/searchcursor');
|
||||||
require('codemirror/addon/search/jump-to-line');
|
require('codemirror/keymap/sublime');
|
||||||
require('codemirror/addon/dialog/dialog');
|
|
||||||
|
|
||||||
require('codemirror-graphql/hint');
|
require('codemirror-graphql/hint');
|
||||||
require('codemirror-graphql/lint');
|
|
||||||
require('codemirror-graphql/info');
|
require('codemirror-graphql/info');
|
||||||
require('codemirror-graphql/jump');
|
require('codemirror-graphql/jump');
|
||||||
|
require('codemirror-graphql/lint');
|
||||||
require('codemirror-graphql/mode');
|
require('codemirror-graphql/mode');
|
||||||
|
|
||||||
require('utils/codemirror/brunoVarInfo');
|
require('utils/codemirror/brunoVarInfo');
|
||||||
|
@ -51,7 +51,8 @@ export const HotkeysProvider = (props) => {
|
|||||||
if (item && item.uid) {
|
if (item && item.uid) {
|
||||||
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
dispatch(saveRequest(activeTab.uid, activeTab.collectionUid));
|
||||||
} else {
|
} else {
|
||||||
setShowSaveRequestModal(true);
|
// todo: when ephermal requests go live
|
||||||
|
// setShowSaveRequestModal(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@ import {
|
|||||||
|
|
||||||
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
import { closeAllCollectionTabs } from 'providers/ReduxStore/slices/tabs';
|
||||||
import { resolveRequestFilename } from 'utils/common/platform';
|
import { resolveRequestFilename } from 'utils/common/platform';
|
||||||
|
import { parseQueryParams, splitOnFirst } from 'utils/url/index';
|
||||||
|
import { each } from 'lodash';
|
||||||
|
|
||||||
const PATH_SEPARATOR = path.sep;
|
const PATH_SEPARATOR = path.sep;
|
||||||
|
|
||||||
@ -82,8 +84,35 @@ export const saveRequest = (itemUid, collectionUid) => (dispatch, getState) => {
|
|||||||
itemSchema
|
itemSchema
|
||||||
.validate(itemToSave)
|
.validate(itemToSave)
|
||||||
.then(() => ipcRenderer.invoke('renderer:save-request', item.pathname, itemToSave))
|
.then(() => ipcRenderer.invoke('renderer:save-request', item.pathname, itemToSave))
|
||||||
|
.then(() => toast.success('Request saved successfully'))
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch((err) => {
|
||||||
|
toast.error('Failed to save request!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveCollectionRoot = (collectionUid) => (dispatch, getState) => {
|
||||||
|
const state = getState();
|
||||||
|
const collection = findCollectionByUid(state.collections.collections, collectionUid);
|
||||||
|
console.log(collection.root);
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!collection) {
|
||||||
|
return reject(new Error('Collection not found'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
|
ipcRenderer
|
||||||
|
.invoke('renderer:save-collection-root', collection.pathname, collection.root)
|
||||||
|
.then(() => toast.success('Collection Settings saved successfully'))
|
||||||
|
.then(resolve)
|
||||||
|
.catch((err) => {
|
||||||
|
toast.error('Failed to save collection settings!');
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -561,6 +590,12 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
return reject(new Error('Collection not found'));
|
return reject(new Error('Collection not found'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parts = splitOnFirst(requestUrl, '?');
|
||||||
|
const params = parseQueryParams(parts[1]);
|
||||||
|
each(params, (urlParam) => {
|
||||||
|
urlParam.enabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
const collectionCopy = cloneDeep(collection);
|
const collectionCopy = cloneDeep(collection);
|
||||||
const item = {
|
const item = {
|
||||||
uid: uuid(),
|
uid: uuid(),
|
||||||
@ -570,11 +605,13 @@ export const newHttpRequest = (params) => (dispatch, getState) => {
|
|||||||
method: requestMethod,
|
method: requestMethod,
|
||||||
url: requestUrl,
|
url: requestUrl,
|
||||||
headers: [],
|
headers: [],
|
||||||
|
params,
|
||||||
body: {
|
body: {
|
||||||
mode: 'none',
|
mode: 'none',
|
||||||
json: null,
|
json: null,
|
||||||
text: null,
|
text: null,
|
||||||
xml: null,
|
xml: null,
|
||||||
|
sparql: null,
|
||||||
multipartForm: null,
|
multipartForm: null,
|
||||||
formUrlEncoded: null
|
formUrlEncoded: null
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ import concat from 'lodash/concat';
|
|||||||
import filter from 'lodash/filter';
|
import filter from 'lodash/filter';
|
||||||
import each from 'lodash/each';
|
import each from 'lodash/each';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
|
import get from 'lodash/get';
|
||||||
|
import set from 'lodash/set';
|
||||||
import { createSlice } from '@reduxjs/toolkit';
|
import { createSlice } from '@reduxjs/toolkit';
|
||||||
import { splitOnFirst } from 'utils/url';
|
import { splitOnFirst } from 'utils/url';
|
||||||
import {
|
import {
|
||||||
@ -40,6 +42,8 @@ export const collectionsSlice = createSlice({
|
|||||||
const collectionUids = map(state.collections, (c) => c.uid);
|
const collectionUids = map(state.collections, (c) => c.uid);
|
||||||
const collection = action.payload;
|
const collection = action.payload;
|
||||||
|
|
||||||
|
collection.settingsSelectedTab = 'headers';
|
||||||
|
|
||||||
// TODO: move this to use the nextAction approach
|
// TODO: move this to use the nextAction approach
|
||||||
// last action is used to track the last action performed on the collection
|
// last action is used to track the last action performed on the collection
|
||||||
// this is optional
|
// this is optional
|
||||||
@ -107,6 +111,15 @@ export const collectionsSlice = createSlice({
|
|||||||
collection.nextAction = nextAction;
|
collection.nextAction = nextAction;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateSettingsSelectedTab: (state, action) => {
|
||||||
|
const { collectionUid, tab } = action.payload;
|
||||||
|
|
||||||
|
const collection = findCollectionByUid(state.collections, collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
collection.settingsSelectedTab = tab;
|
||||||
|
}
|
||||||
|
},
|
||||||
collectionUnlinkEnvFileEvent: (state, action) => {
|
collectionUnlinkEnvFileEvent: (state, action) => {
|
||||||
const { data: environment, meta } = action.payload;
|
const { data: environment, meta } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, meta.collectionUid);
|
||||||
@ -258,10 +271,27 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
deleteRequestDraft: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const item = findItemInCollection(collection, action.payload.itemUid);
|
||||||
|
|
||||||
|
if (item && item.draft) {
|
||||||
|
item.draft = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
newEphemeralHttpRequest: (state, action) => {
|
newEphemeralHttpRequest: (state, action) => {
|
||||||
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
if (collection && collection.items && collection.items.length) {
|
if (collection && collection.items && collection.items.length) {
|
||||||
|
const parts = splitOnFirst(action.payload.requestUrl, '?');
|
||||||
|
const params = parseQueryParams(parts[1]);
|
||||||
|
each(params, (urlParam) => {
|
||||||
|
urlParam.enabled = true;
|
||||||
|
});
|
||||||
|
|
||||||
const item = {
|
const item = {
|
||||||
uid: action.payload.uid,
|
uid: action.payload.uid,
|
||||||
name: action.payload.requestName,
|
name: action.payload.requestName,
|
||||||
@ -269,7 +299,7 @@ export const collectionsSlice = createSlice({
|
|||||||
request: {
|
request: {
|
||||||
url: action.payload.requestUrl,
|
url: action.payload.requestUrl,
|
||||||
method: action.payload.requestMethod,
|
method: action.payload.requestMethod,
|
||||||
params: [],
|
params,
|
||||||
headers: [],
|
headers: [],
|
||||||
body: {
|
body: {
|
||||||
mode: null,
|
mode: null,
|
||||||
@ -672,6 +702,10 @@ export const collectionsSlice = createSlice({
|
|||||||
item.draft.request.body.xml = action.payload.content;
|
item.draft.request.body.xml = action.payload.content;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'sparql': {
|
||||||
|
item.draft.request.body.sparql = action.payload.content;
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'formUrlEncoded': {
|
case 'formUrlEncoded': {
|
||||||
item.draft.request.body.formUrlEncoded = action.payload.content;
|
item.draft.request.body.formUrlEncoded = action.payload.content;
|
||||||
break;
|
break;
|
||||||
@ -923,10 +957,100 @@ export const collectionsSlice = createSlice({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
updateCollectionAuthMode: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.auth.mode', action.payload.mode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionAuth: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
switch (action.payload.mode) {
|
||||||
|
case 'bearer':
|
||||||
|
set(collection, 'root.request.auth.bearer', action.payload.content);
|
||||||
|
break;
|
||||||
|
case 'basic':
|
||||||
|
set(collection, 'root.request.auth.basic', action.payload.content);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionRequestScript: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.script.req', action.payload.script);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionResponseScript: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.script.res', action.payload.script);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCollectionTests: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
set(collection, 'root.request.tests', action.payload.tests);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
headers.push({
|
||||||
|
uid: uuid(),
|
||||||
|
name: '',
|
||||||
|
value: '',
|
||||||
|
description: '',
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
set(collection, 'root.request.headers', headers);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
const headers = get(collection, 'root.request.headers', []);
|
||||||
|
const header = find(headers, (h) => h.uid === action.payload.header.uid);
|
||||||
|
if (header) {
|
||||||
|
header.name = action.payload.header.name;
|
||||||
|
header.value = action.payload.header.value;
|
||||||
|
header.description = action.payload.header.description;
|
||||||
|
header.enabled = action.payload.header.enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteCollectionHeader: (state, action) => {
|
||||||
|
const collection = findCollectionByUid(state.collections, action.payload.collectionUid);
|
||||||
|
|
||||||
|
if (collection) {
|
||||||
|
let headers = get(collection, 'root.request.headers', []);
|
||||||
|
headers = filter(headers, (h) => h.uid !== action.payload.headerUid);
|
||||||
|
set(collection, 'root.request.headers', headers);
|
||||||
|
}
|
||||||
|
},
|
||||||
collectionAddFileEvent: (state, action) => {
|
collectionAddFileEvent: (state, action) => {
|
||||||
const file = action.payload.file;
|
const file = action.payload.file;
|
||||||
|
const isCollectionRoot = file.meta.collectionRoot ? true : false;
|
||||||
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
||||||
|
|
||||||
|
if (isCollectionRoot) {
|
||||||
|
if (collection) {
|
||||||
|
collection.root = file.data;
|
||||||
|
}
|
||||||
|
console.log('collectionAddFileEvent', file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const dirname = getDirectoryName(file.meta.pathname);
|
const dirname = getDirectoryName(file.meta.pathname);
|
||||||
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
const subDirectories = getSubdirectoriesFromRoot(collection.pathname, dirname);
|
||||||
@ -1011,6 +1135,12 @@ export const collectionsSlice = createSlice({
|
|||||||
const { file } = action.payload;
|
const { file } = action.payload;
|
||||||
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
const collection = findCollectionByUid(state.collections, file.meta.collectionUid);
|
||||||
|
|
||||||
|
// check and update collection root
|
||||||
|
if (collection && file.meta.collectionRoot) {
|
||||||
|
collection.root = file.data;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (collection) {
|
if (collection) {
|
||||||
const item = findItemInCollection(collection, file.data.uid);
|
const item = findItemInCollection(collection, file.data.uid);
|
||||||
|
|
||||||
@ -1107,7 +1237,6 @@ export const collectionsSlice = createSlice({
|
|||||||
const { cancelTokenUid } = action.payload;
|
const { cancelTokenUid } = action.payload;
|
||||||
item.requestUid = requestUid;
|
item.requestUid = requestUid;
|
||||||
item.requestState = 'queued';
|
item.requestState = 'queued';
|
||||||
item.response = null;
|
|
||||||
item.cancelTokenUid = cancelTokenUid;
|
item.cancelTokenUid = cancelTokenUid;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1216,6 +1345,7 @@ export const {
|
|||||||
sortCollections,
|
sortCollections,
|
||||||
updateLastAction,
|
updateLastAction,
|
||||||
updateNextAction,
|
updateNextAction,
|
||||||
|
updateSettingsSelectedTab,
|
||||||
collectionUnlinkEnvFileEvent,
|
collectionUnlinkEnvFileEvent,
|
||||||
saveEnvironment,
|
saveEnvironment,
|
||||||
selectEnvironment,
|
selectEnvironment,
|
||||||
@ -1228,6 +1358,7 @@ export const {
|
|||||||
requestCancelled,
|
requestCancelled,
|
||||||
responseReceived,
|
responseReceived,
|
||||||
saveRequest,
|
saveRequest,
|
||||||
|
deleteRequestDraft,
|
||||||
newEphemeralHttpRequest,
|
newEphemeralHttpRequest,
|
||||||
collectionClicked,
|
collectionClicked,
|
||||||
collectionFolderClicked,
|
collectionFolderClicked,
|
||||||
@ -1260,6 +1391,14 @@ export const {
|
|||||||
addVar,
|
addVar,
|
||||||
updateVar,
|
updateVar,
|
||||||
deleteVar,
|
deleteVar,
|
||||||
|
addCollectionHeader,
|
||||||
|
updateCollectionHeader,
|
||||||
|
deleteCollectionHeader,
|
||||||
|
updateCollectionAuthMode,
|
||||||
|
updateCollectionAuth,
|
||||||
|
updateCollectionRequestScript,
|
||||||
|
updateCollectionResponseScript,
|
||||||
|
updateCollectionTests,
|
||||||
collectionAddFileEvent,
|
collectionAddFileEvent,
|
||||||
collectionAddDirectoryEvent,
|
collectionAddDirectoryEvent,
|
||||||
collectionChangeFileEvent,
|
collectionChangeFileEvent,
|
||||||
|
@ -53,3 +53,12 @@ body::-webkit-scrollbar-thumb,
|
|||||||
background-color: #cdcdcd;
|
background-color: #cdcdcd;
|
||||||
border-radius: 5rem;
|
border-radius: 5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* todo: this will be supported in the future to be changed via applying a theme
|
||||||
|
* making all the checkboxes and radios bigger
|
||||||
|
* input[type='checkbox'],
|
||||||
|
* input[type='radio'] {
|
||||||
|
* transform: scale(1.1);
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
@ -86,7 +86,11 @@ const darkTheme = {
|
|||||||
get: '#8cd656',
|
get: '#8cd656',
|
||||||
post: '#cd56d6',
|
post: '#cd56d6',
|
||||||
put: '#d69956',
|
put: '#d69956',
|
||||||
delete: '#f06f57'
|
delete: '#f06f57',
|
||||||
|
// customize these colors if needed
|
||||||
|
patch: '#d69956',
|
||||||
|
options: '#d69956',
|
||||||
|
head: '#d69956'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -105,7 +109,8 @@ const darkTheme = {
|
|||||||
responseSendIcon: '#555',
|
responseSendIcon: '#555',
|
||||||
responseStatus: '#ccc',
|
responseStatus: '#ccc',
|
||||||
responseOk: '#8cd656',
|
responseOk: '#8cd656',
|
||||||
responseError: '#f06f57'
|
responseError: '#f06f57',
|
||||||
|
responseOverlayBg: 'rgba(30, 30, 30, 0.6)'
|
||||||
},
|
},
|
||||||
|
|
||||||
collection: {
|
collection: {
|
||||||
|
@ -86,7 +86,11 @@ const lightTheme = {
|
|||||||
get: 'rgb(5, 150, 105)',
|
get: 'rgb(5, 150, 105)',
|
||||||
post: '#8e44ad',
|
post: '#8e44ad',
|
||||||
put: '#ca7811',
|
put: '#ca7811',
|
||||||
delete: 'rgb(185, 28, 28)'
|
delete: 'rgb(185, 28, 28)',
|
||||||
|
// customize these colors if needed
|
||||||
|
patch: '#ca7811',
|
||||||
|
options: '#ca7811',
|
||||||
|
head: '#ca7811'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -105,7 +109,8 @@ const lightTheme = {
|
|||||||
responseSendIcon: 'rgb(209, 213, 219)',
|
responseSendIcon: 'rgb(209, 213, 219)',
|
||||||
responseStatus: 'rgb(117 117 117)',
|
responseStatus: 'rgb(117 117 117)',
|
||||||
responseOk: '#047857',
|
responseOk: '#047857',
|
||||||
responseError: 'rgb(185, 28, 28)'
|
responseError: 'rgb(185, 28, 28)',
|
||||||
|
responseOverlayBg: 'rgba(255, 255, 255, 0.6)'
|
||||||
},
|
},
|
||||||
|
|
||||||
collection: {
|
collection: {
|
||||||
|
@ -138,7 +138,7 @@ export const moveCollectionItem = (collection, draggedItem, targetItem) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetItem.type === 'folder') {
|
if (targetItem.type === 'folder') {
|
||||||
targetItem.items = targetItem.items || [];
|
targetItem.items = sortBy(targetItem.items || [], (item) => item.seq);
|
||||||
targetItem.items.push(draggedItem);
|
targetItem.items.push(draggedItem);
|
||||||
draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename);
|
draggedItem.pathname = path.join(targetItem.pathname, draggedItem.filename);
|
||||||
} else {
|
} else {
|
||||||
@ -166,7 +166,9 @@ export const moveCollectionItemToRootOfCollection = (collection, draggedItem) =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
draggedItemParent.items = sortBy(draggedItemParent.items, (item) => item.seq);
|
||||||
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
draggedItemParent.items = filter(draggedItemParent.items, (i) => i.uid !== draggedItem.uid);
|
||||||
|
collection.items = sortBy(collection.items, (item) => item.seq);
|
||||||
collection.items.push(draggedItem);
|
collection.items.push(draggedItem);
|
||||||
if (draggedItem.type == 'folder') {
|
if (draggedItem.type == 'folder') {
|
||||||
draggedItem.pathname = path.join(collection.pathname, draggedItem.name);
|
draggedItem.pathname = path.join(collection.pathname, draggedItem.name);
|
||||||
@ -282,6 +284,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
|||||||
text: si.draft.request.body.text,
|
text: si.draft.request.body.text,
|
||||||
xml: si.draft.request.body.xml,
|
xml: si.draft.request.body.xml,
|
||||||
graphql: si.draft.request.body.graphql,
|
graphql: si.draft.request.body.graphql,
|
||||||
|
sparql: si.draft.request.body.sparql,
|
||||||
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded),
|
formUrlEncoded: copyFormUrlEncodedParams(si.draft.request.body.formUrlEncoded),
|
||||||
multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm)
|
multipartForm: copyMultipartFormParams(si.draft.request.body.multipartForm)
|
||||||
},
|
},
|
||||||
@ -314,6 +317,7 @@ export const transformCollectionToSaveToExportAsFile = (collection, options = {}
|
|||||||
text: si.request.body.text,
|
text: si.request.body.text,
|
||||||
xml: si.request.body.xml,
|
xml: si.request.body.xml,
|
||||||
graphql: si.request.body.graphql,
|
graphql: si.request.body.graphql,
|
||||||
|
sparql: si.request.body.sparql,
|
||||||
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
|
formUrlEncoded: copyFormUrlEncodedParams(si.request.body.formUrlEncoded),
|
||||||
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
|
multipartForm: copyMultipartFormParams(si.request.body.multipartForm)
|
||||||
},
|
},
|
||||||
@ -457,6 +461,10 @@ export const humanizeRequestBodyMode = (mode) => {
|
|||||||
label = 'XML';
|
label = 'XML';
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case 'sparql': {
|
||||||
|
label = 'SPARQL';
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 'formUrlEncoded': {
|
case 'formUrlEncoded': {
|
||||||
label = 'Form URL Encoded';
|
label = 'Form URL Encoded';
|
||||||
break;
|
break;
|
||||||
|
@ -60,6 +60,8 @@ export const getCodeMirrorModeBasedOnContentType = (contentType) => {
|
|||||||
return 'application/xml';
|
return 'application/xml';
|
||||||
} else if (contentType.includes('yaml')) {
|
} else if (contentType.includes('yaml')) {
|
||||||
return 'application/yaml';
|
return 'application/yaml';
|
||||||
|
} else if (contentType.includes('image')) {
|
||||||
|
return 'application/image';
|
||||||
} else {
|
} else {
|
||||||
return 'application/text';
|
return 'application/text';
|
||||||
}
|
}
|
||||||
|
@ -41,3 +41,10 @@ export const isWindowsOS = () => {
|
|||||||
|
|
||||||
return osFamily.includes('windows');
|
return osFamily.includes('windows');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isMacOS = () => {
|
||||||
|
const os = platform.os;
|
||||||
|
const osFamily = os.family.toLowerCase();
|
||||||
|
|
||||||
|
return osFamily.includes('os x');
|
||||||
|
};
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { safeStringifyJSON } from 'utils/common';
|
||||||
|
|
||||||
export const sendNetworkRequest = async (item, collection, environment, collectionVariables) => {
|
export const sendNetworkRequest = async (item, collection, environment, collectionVariables) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
if (['http-request', 'graphql-request'].includes(item.type)) {
|
if (['http-request', 'graphql-request'].includes(item.type)) {
|
||||||
@ -7,7 +9,7 @@ export const sendNetworkRequest = async (item, collection, environment, collecti
|
|||||||
state: 'success',
|
state: 'success',
|
||||||
data: response.data,
|
data: response.data,
|
||||||
headers: Object.entries(response.headers),
|
headers: Object.entries(response.headers),
|
||||||
size: response.headers['content-length'] || 0,
|
size: getResponseSize(response),
|
||||||
status: response.status,
|
status: response.status,
|
||||||
statusText: response.statusText,
|
statusText: response.statusText,
|
||||||
duration: response.duration
|
duration: response.duration
|
||||||
@ -23,20 +25,21 @@ const sendHttpRequest = async (item, collection, environment, collectionVariable
|
|||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer
|
||||||
.invoke('send-http-request', item, collection.uid, collection.pathname, environment, collectionVariables)
|
.invoke('send-http-request', item, collection, environment, collectionVariables)
|
||||||
.then(resolve)
|
.then(resolve)
|
||||||
.catch(reject);
|
.catch(reject);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchGqlSchema = async (endpoint, environment, request, collectionVariables) => {
|
const getResponseSize = (response) => {
|
||||||
|
return response.headers['content-length'] || Buffer.byteLength(safeStringifyJSON(response.data)) || 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const fetchGqlSchema = async (endpoint, environment, request, collection) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const { ipcRenderer } = window;
|
const { ipcRenderer } = window;
|
||||||
|
|
||||||
ipcRenderer
|
ipcRenderer.invoke('fetch-gql-schema', endpoint, environment, request, collection).then(resolve).catch(reject);
|
||||||
.invoke('fetch-gql-schema', endpoint, environment, request, collectionVariables)
|
|
||||||
.then(resolve)
|
|
||||||
.catch(reject);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,5 +1,9 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.13.0
|
||||||
|
|
||||||
|
- feat(#306) Module whitelisting and filesystem access support
|
||||||
|
|
||||||
## 0.12.0
|
## 0.12.0
|
||||||
|
|
||||||
- show response time in milliseconds per request and total
|
- show response time in milliseconds per request and total
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@usebruno/cli",
|
"name": "@usebruno/cli",
|
||||||
"version": "0.12.0",
|
"version": "0.14.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -24,8 +24,8 @@
|
|||||||
"package.json"
|
"package.json"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
"chalk": "^3.0.0",
|
"chalk": "^3.0.0",
|
||||||
@ -39,6 +39,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"yargs": "^17.6.2"
|
"yargs": "^17.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ const { exists, isFile, isDirectory } = require('../utils/filesystem');
|
|||||||
const { runSingleRequest } = require('../runner/run-single-request');
|
const { runSingleRequest } = require('../runner/run-single-request');
|
||||||
const { bruToEnvJson, getEnvVars } = require('../utils/bru');
|
const { bruToEnvJson, getEnvVars } = require('../utils/bru');
|
||||||
const { rpad } = require('../utils/common');
|
const { rpad } = require('../utils/common');
|
||||||
const { bruToJson, getOptions } = require('../utils/bru');
|
const { bruToJson, getOptions, collectionBruToJson } = require('../utils/bru');
|
||||||
const { dotenvToJson } = require('@usebruno/lang');
|
const { dotenvToJson } = require('@usebruno/lang');
|
||||||
|
|
||||||
const command = 'run [filename]';
|
const command = 'run [filename]';
|
||||||
@ -121,6 +121,9 @@ const getBruFilesRecursively = (dir) => {
|
|||||||
|
|
||||||
const currentDirBruJsons = [];
|
const currentDirBruJsons = [];
|
||||||
for (const file of filesInCurrentDir) {
|
for (const file of filesInCurrentDir) {
|
||||||
|
if (['collection.bru', 'folder.bru'].includes(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
const filePath = path.join(currentPath, file);
|
const filePath = path.join(currentPath, file);
|
||||||
const stats = fs.lstatSync(filePath);
|
const stats = fs.lstatSync(filePath);
|
||||||
|
|
||||||
@ -151,6 +154,19 @@ const getBruFilesRecursively = (dir) => {
|
|||||||
return getFilesInOrder(dir);
|
return getFilesInOrder(dir);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCollectionRoot = (dir) => {
|
||||||
|
const collectionRootPath = path.join(dir, 'collection.bru');
|
||||||
|
const exists = fs.existsSync(collectionRootPath);
|
||||||
|
if (!exists) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = fs.readFileSync(collectionRootPath, 'utf8');
|
||||||
|
const json = collectionBruToJson(content);
|
||||||
|
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
|
||||||
const builder = async (yargs) => {
|
const builder = async (yargs) => {
|
||||||
yargs
|
yargs
|
||||||
.option('r', {
|
.option('r', {
|
||||||
@ -210,6 +226,7 @@ const handler = async function (argv) {
|
|||||||
|
|
||||||
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
const brunoConfigFile = fs.readFileSync(brunoJsonPath, 'utf8');
|
||||||
const brunoConfig = JSON.parse(brunoConfigFile);
|
const brunoConfig = JSON.parse(brunoConfigFile);
|
||||||
|
const collectionRoot = getCollectionRoot(collectionPath);
|
||||||
|
|
||||||
if (filename && filename.length) {
|
if (filename && filename.length) {
|
||||||
const pathExists = await exists(filename);
|
const pathExists = await exists(filename);
|
||||||
@ -349,7 +366,8 @@ const handler = async function (argv) {
|
|||||||
collectionVariables,
|
collectionVariables,
|
||||||
envVars,
|
envVars,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
brunoConfig
|
brunoConfig,
|
||||||
|
collectionRoot
|
||||||
);
|
);
|
||||||
|
|
||||||
results.push(result);
|
results.push(result);
|
||||||
|
55
packages/bruno-cli/src/runner/interpolate-string.js
Normal file
55
packages/bruno-cli/src/runner/interpolate-string.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
const Handlebars = require('handlebars');
|
||||||
|
const { forOwn, cloneDeep } = require('lodash');
|
||||||
|
|
||||||
|
const interpolateEnvVars = (str, processEnvVars) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
return template({
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const interpolateString = (str, { envVars, collectionVariables, processEnvVars }) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
processEnvVars = processEnvVars || {};
|
||||||
|
collectionVariables = collectionVariables || {};
|
||||||
|
|
||||||
|
// we clone envVars because we don't want to modify the original object
|
||||||
|
envVars = envVars ? cloneDeep(envVars) : {};
|
||||||
|
|
||||||
|
// envVars can inturn have values as {{process.env.VAR_NAME}}
|
||||||
|
// so we need to interpolate envVars first with processEnvVars
|
||||||
|
forOwn(envVars, (value, key) => {
|
||||||
|
envVars[key] = interpolateEnvVars(value, processEnvVars);
|
||||||
|
});
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
// collectionVariables take precedence over envVars
|
||||||
|
const combinedVars = {
|
||||||
|
...envVars,
|
||||||
|
...collectionVariables,
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return template(combinedVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interpolateString
|
||||||
|
};
|
@ -1,9 +1,20 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter } = require('lodash');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
|
|
||||||
const prepareRequest = (request) => {
|
const prepareRequest = (request, collectionRoot) => {
|
||||||
const headers = {};
|
const headers = {};
|
||||||
let contentTypeDefined = false;
|
let contentTypeDefined = false;
|
||||||
|
|
||||||
|
// collection headers
|
||||||
|
each(get(collectionRoot, 'request.headers', []), (h) => {
|
||||||
|
if (h.enabled) {
|
||||||
|
headers[h.name] = h.value;
|
||||||
|
if (h.name.toLowerCase() === 'content-type') {
|
||||||
|
contentTypeDefined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
each(request.headers, (h) => {
|
each(request.headers, (h) => {
|
||||||
if (h.enabled) {
|
if (h.enabled) {
|
||||||
headers[h.name] = h.value;
|
headers[h.name] = h.value;
|
||||||
@ -20,6 +31,23 @@ const prepareRequest = (request) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
|
// A request can override the collection auth with another auth
|
||||||
|
// But it cannot override the collection auth with no auth
|
||||||
|
// We will provide support for disabling the auth via scripting in the future
|
||||||
|
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||||
|
if (collectionAuth) {
|
||||||
|
if (collectionAuth.mode === 'basic') {
|
||||||
|
axiosRequest.auth = {
|
||||||
|
username: get(collectionAuth, 'basic.username'),
|
||||||
|
password: get(collectionAuth, 'basic.password')
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collectionAuth.mode === 'bearer') {
|
||||||
|
axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (request.auth) {
|
if (request.auth) {
|
||||||
if (request.auth.mode === 'basic') {
|
if (request.auth.mode === 'basic') {
|
||||||
axiosRequest.auth = {
|
axiosRequest.auth = {
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
|
const os = require('os');
|
||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { forOwn, each, extend, get } = require('lodash');
|
const { forOwn, each, extend, get, compact } = require('lodash');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
const interpolateVars = require('./interpolate-vars');
|
const interpolateVars = require('./interpolate-vars');
|
||||||
|
const { interpolateString } = require('./interpolate-string');
|
||||||
const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js');
|
const { ScriptRuntime, TestRuntime, VarsRuntime, AssertRuntime } = require('@usebruno/js');
|
||||||
const { stripExtension } = require('../utils/filesystem');
|
const { stripExtension } = require('../utils/filesystem');
|
||||||
const { getOptions } = require('../utils/bru');
|
const { getOptions } = require('../utils/bru');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||||
|
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||||
const { makeAxiosInstance } = require('../utils/axios-instance');
|
const { makeAxiosInstance } = require('../utils/axios-instance');
|
||||||
|
|
||||||
const runSingleRequest = async function (
|
const runSingleRequest = async function (
|
||||||
@ -21,13 +24,16 @@ const runSingleRequest = async function (
|
|||||||
collectionVariables,
|
collectionVariables,
|
||||||
envVariables,
|
envVariables,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
brunoConfig
|
brunoConfig,
|
||||||
|
collectionRoot
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
let request;
|
let request;
|
||||||
|
|
||||||
request = prepareRequest(bruJson.request);
|
request = prepareRequest(bruJson.request);
|
||||||
|
|
||||||
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
|
||||||
// make axios work in node using form data
|
// make axios work in node using form data
|
||||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
||||||
@ -54,7 +60,10 @@ const runSingleRequest = async function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run pre request script
|
// run pre request script
|
||||||
const requestScriptFile = get(bruJson, 'request.script.req');
|
const requestScriptFile = compact([
|
||||||
|
get(collectionRoot, 'request.script.req'),
|
||||||
|
get(bruJson, 'request.script.req')
|
||||||
|
]).join(os.EOL);
|
||||||
if (requestScriptFile && requestScriptFile.length) {
|
if (requestScriptFile && requestScriptFile.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
await scriptRuntime.runRequestScript(
|
await scriptRuntime.runRequestScript(
|
||||||
@ -64,7 +73,8 @@ const runSingleRequest = async function (
|
|||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
null,
|
null,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,28 +102,44 @@ const runSingleRequest = async function (
|
|||||||
// set proxy if enabled
|
// set proxy if enabled
|
||||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||||
if (proxyEnabled) {
|
if (proxyEnabled) {
|
||||||
const proxyProtocol = get(brunoConfig, 'proxy.protocol');
|
let proxyUri;
|
||||||
const proxyHostname = get(brunoConfig, 'proxy.hostname');
|
const interpolationOptions = {
|
||||||
const proxyPort = get(brunoConfig, 'proxy.port');
|
envVars: envVariables,
|
||||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
collectionVariables,
|
||||||
|
processEnvVars
|
||||||
|
};
|
||||||
|
|
||||||
let proxy;
|
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
||||||
|
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
||||||
|
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
|
||||||
|
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||||
|
const socksEnabled = proxyProtocol.includes('socks');
|
||||||
|
|
||||||
|
interpolateString;
|
||||||
|
|
||||||
if (proxyAuthEnabled) {
|
if (proxyAuthEnabled) {
|
||||||
const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username');
|
const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions);
|
||||||
const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password');
|
const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions);
|
||||||
|
|
||||||
proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
||||||
} else {
|
} else {
|
||||||
proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.httpsAgent = new HttpsProxyAgent(
|
if (socksEnabled) {
|
||||||
proxy,
|
const socksProxyAgent = new SocksProxyAgent(proxyUri);
|
||||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
request.httpAgent = new HttpProxyAgent(proxy);
|
request.httpsAgent = socksProxyAgent;
|
||||||
|
|
||||||
|
request.httpAgent = socksProxyAgent;
|
||||||
|
} else {
|
||||||
|
request.httpsAgent = new HttpsProxyAgent(
|
||||||
|
proxyUri,
|
||||||
|
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||||
|
}
|
||||||
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
||||||
request.httpsAgent = new https.Agent({
|
request.httpsAgent = new https.Agent({
|
||||||
...httpsAgentRequestFields
|
...httpsAgentRequestFields
|
||||||
@ -187,7 +213,10 @@ const runSingleRequest = async function (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run post response script
|
// run post response script
|
||||||
const responseScriptFile = get(bruJson, 'request.script.res');
|
const responseScriptFile = compact([
|
||||||
|
get(collectionRoot, 'request.script.res'),
|
||||||
|
get(bruJson, 'request.script.res')
|
||||||
|
]).join(os.EOL);
|
||||||
if (responseScriptFile && responseScriptFile.length) {
|
if (responseScriptFile && responseScriptFile.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
await scriptRuntime.runResponseScript(
|
await scriptRuntime.runResponseScript(
|
||||||
@ -198,7 +227,8 @@ const runSingleRequest = async function (
|
|||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
null,
|
null,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +258,7 @@ const runSingleRequest = async function (
|
|||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
let testResults = [];
|
let testResults = [];
|
||||||
const testFile = get(bruJson, 'request.tests');
|
const testFile = compact([get(collectionRoot, 'request.tests'), get(bruJson, 'request.tests')]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const result = await testRuntime.runTests(
|
const result = await testRuntime.runTests(
|
||||||
@ -239,7 +269,8 @@ const runSingleRequest = async function (
|
|||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
null,
|
null,
|
||||||
processEnvVars
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
);
|
);
|
||||||
testResults = get(result, 'results', []);
|
testResults = get(result, 'results', []);
|
||||||
}
|
}
|
||||||
@ -273,6 +304,7 @@ const runSingleRequest = async function (
|
|||||||
testResults
|
testResults
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log(chalk.red(stripExtension(filename)) + chalk.dim(` (${err.message})`));
|
||||||
return {
|
return {
|
||||||
request: {
|
request: {
|
||||||
method: null,
|
method: null,
|
||||||
|
@ -26,7 +26,9 @@ function makeAxiosInstance() {
|
|||||||
if (error.response) {
|
if (error.response) {
|
||||||
const end = Date.now();
|
const end = Date.now();
|
||||||
const start = error.config.headers['request-start-time'];
|
const start = error.config.headers['request-start-time'];
|
||||||
error.response.headers['request-duration'] = end - start;
|
if (error.response) {
|
||||||
|
error.response.headers['request-duration'] = end - start;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const { bruToEnvJsonV2, bruToJsonV2 } = require('@usebruno/lang');
|
const { bruToEnvJsonV2, bruToJsonV2, collectionBruToJson: _collectionBruToJson } = require('@usebruno/lang');
|
||||||
|
|
||||||
// override the default escape function to prevent escaping
|
// override the default escape function to prevent escaping
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
return value;
|
return value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const collectionBruToJson = (bru) => {
|
||||||
|
try {
|
||||||
|
const json = _collectionBruToJson(bru);
|
||||||
|
|
||||||
|
const transformedJson = {
|
||||||
|
request: {
|
||||||
|
params: _.get(json, 'query', []),
|
||||||
|
headers: _.get(json, 'headers', []),
|
||||||
|
auth: _.get(json, 'auth', {}),
|
||||||
|
script: _.get(json, 'script', {}),
|
||||||
|
vars: _.get(json, 'vars', {}),
|
||||||
|
tests: _.get(json, 'tests', '')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedJson;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transformer function for converting a BRU file to JSON.
|
* The transformer function for converting a BRU file to JSON.
|
||||||
*
|
*
|
||||||
@ -91,5 +112,6 @@ module.exports = {
|
|||||||
bruToJson,
|
bruToJson,
|
||||||
bruToEnvJson,
|
bruToEnvJson,
|
||||||
getEnvVars,
|
getEnvVars,
|
||||||
getOptions
|
getOptions,
|
||||||
|
collectionBruToJson
|
||||||
};
|
};
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "v0.20.0",
|
"version": "v0.23.0",
|
||||||
"name": "bruno",
|
"name": "bruno",
|
||||||
"description": "Opensource API Client for Exploring and Testing APIs",
|
"description": "Opensource API Client for Exploring and Testing APIs",
|
||||||
"homepage": "https://www.usebruno.com",
|
"homepage": "https://www.usebruno.com",
|
||||||
@ -15,13 +15,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aws-sdk/credential-providers": "^3.425.0",
|
"@aws-sdk/credential-providers": "^3.425.0",
|
||||||
"@usebruno/js": "0.6.0",
|
"@usebruno/js": "0.8.0",
|
||||||
"@usebruno/lang": "0.5.0",
|
"@usebruno/lang": "0.8.0",
|
||||||
"@usebruno/schema": "0.5.0",
|
"@usebruno/schema": "0.5.0",
|
||||||
"about-window": "^1.15.2",
|
"about-window": "^1.15.2",
|
||||||
"aws4-axios": "^3.3.0",
|
"aws4-axios": "^3.3.0",
|
||||||
"axios": "^1.5.1",
|
"axios": "^1.5.1",
|
||||||
"chai": "^4.3.7",
|
"chai": "^4.3.7",
|
||||||
|
"chai-string": "^1.5.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"decomment": "^0.9.5",
|
"decomment": "^0.9.5",
|
||||||
"dotenv": "^16.0.3",
|
"dotenv": "^16.0.3",
|
||||||
@ -41,6 +42,7 @@
|
|||||||
"nanoid": "3.3.4",
|
"nanoid": "3.3.4",
|
||||||
"node-machine-id": "^1.1.12",
|
"node-machine-id": "^1.1.12",
|
||||||
"qs": "^6.11.0",
|
"qs": "^6.11.0",
|
||||||
|
"socks-proxy-agent": "^8.0.2",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vm2": "^3.9.13",
|
"vm2": "^3.9.13",
|
||||||
"yup": "^0.32.11"
|
"yup": "^0.32.11"
|
||||||
|
@ -24,7 +24,10 @@ const template = [
|
|||||||
{ role: 'cut' },
|
{ role: 'cut' },
|
||||||
{ role: 'copy' },
|
{ role: 'copy' },
|
||||||
{ role: 'paste' },
|
{ role: 'paste' },
|
||||||
{ role: 'selectAll' }
|
{ role: 'selectAll' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'hide' },
|
||||||
|
{ role: 'hideOthers' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2,12 +2,10 @@ const _ = require('lodash');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const { hasJsonExtension, hasBruExtension, writeFile } = require('../utils/filesystem');
|
const { hasBruExtension } = require('../utils/filesystem');
|
||||||
const { bruToEnvJson, envJsonToBru, bruToJson, jsonToBru } = require('../bru');
|
const { bruToEnvJson, bruToJson, collectionBruToJson } = require('../bru');
|
||||||
const { dotenvToJson } = require('@usebruno/lang');
|
const { dotenvToJson } = require('@usebruno/lang');
|
||||||
|
|
||||||
const { isLegacyEnvFile, migrateLegacyEnvFile, isLegacyBruFile, migrateLegacyBruFile } = require('../bru/migrate');
|
|
||||||
const { itemSchema } = require('@usebruno/schema');
|
|
||||||
const { uuid } = require('../utils/common');
|
const { uuid } = require('../utils/common');
|
||||||
const { getRequestUid } = require('../cache/requestUids');
|
const { getRequestUid } = require('../cache/requestUids');
|
||||||
const { decryptString } = require('../utils/encryption');
|
const { decryptString } = require('../utils/encryption');
|
||||||
@ -17,13 +15,6 @@ const EnvironmentSecretsStore = require('../store/env-secrets');
|
|||||||
|
|
||||||
const environmentSecretsStore = new EnvironmentSecretsStore();
|
const environmentSecretsStore = new EnvironmentSecretsStore();
|
||||||
|
|
||||||
const isJsonEnvironmentConfig = (pathname, collectionPath) => {
|
|
||||||
const dirname = path.dirname(pathname);
|
|
||||||
const basename = path.basename(pathname);
|
|
||||||
|
|
||||||
return dirname === collectionPath && basename === 'environments.json';
|
|
||||||
};
|
|
||||||
|
|
||||||
const isDotEnvFile = (pathname, collectionPath) => {
|
const isDotEnvFile = (pathname, collectionPath) => {
|
||||||
const dirname = path.dirname(pathname);
|
const dirname = path.dirname(pathname);
|
||||||
const basename = path.basename(pathname);
|
const basename = path.basename(pathname);
|
||||||
@ -46,6 +37,13 @@ const isBruEnvironmentConfig = (pathname, collectionPath) => {
|
|||||||
return dirname === envDirectory && hasBruExtension(basename);
|
return dirname === envDirectory && hasBruExtension(basename);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isCollectionRootBruFile = (pathname, collectionPath) => {
|
||||||
|
const dirname = path.dirname(pathname);
|
||||||
|
const basename = path.basename(pathname);
|
||||||
|
|
||||||
|
return dirname === collectionPath && basename === 'collection.bru';
|
||||||
|
};
|
||||||
|
|
||||||
const hydrateRequestWithUuid = (request, pathname) => {
|
const hydrateRequestWithUuid = (request, pathname) => {
|
||||||
request.uid = getRequestUid(pathname);
|
request.uid = getRequestUid(pathname);
|
||||||
|
|
||||||
@ -68,6 +66,20 @@ const hydrateRequestWithUuid = (request, pathname) => {
|
|||||||
return request;
|
return request;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const hydrateBruCollectionFileWithUuid = (collectionRoot) => {
|
||||||
|
const params = _.get(collectionRoot, 'request.params', []);
|
||||||
|
const headers = _.get(collectionRoot, 'request.headers', []);
|
||||||
|
const requestVars = _.get(collectionRoot, 'request.vars.req', []);
|
||||||
|
const responseVars = _.get(collectionRoot, 'request.vars.res', []);
|
||||||
|
|
||||||
|
params.forEach((param) => (param.uid = uuid()));
|
||||||
|
headers.forEach((header) => (header.uid = uuid()));
|
||||||
|
requestVars.forEach((variable) => (variable.uid = uuid()));
|
||||||
|
responseVars.forEach((variable) => (variable.uid = uuid()));
|
||||||
|
|
||||||
|
return collectionRoot;
|
||||||
|
};
|
||||||
|
|
||||||
const envHasSecrets = (environment = {}) => {
|
const envHasSecrets = (environment = {}) => {
|
||||||
const secrets = _.filter(environment.variables, (v) => v.secret);
|
const secrets = _.filter(environment.variables, (v) => v.secret);
|
||||||
|
|
||||||
@ -87,11 +99,6 @@ const addEnvironmentFile = async (win, pathname, collectionUid, collectionPath)
|
|||||||
|
|
||||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
// migrate old env json to bru file
|
|
||||||
if (isLegacyEnvFile(bruContent)) {
|
|
||||||
bruContent = await migrateLegacyEnvFile(bruContent, pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.data = bruToEnvJson(bruContent);
|
file.data = bruToEnvJson(bruContent);
|
||||||
file.data.name = basename.substring(0, basename.length - 4);
|
file.data.name = basename.substring(0, basename.length - 4);
|
||||||
file.data.uid = getRequestUid(pathname);
|
file.data.uid = getRequestUid(pathname);
|
||||||
@ -205,54 +212,31 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isJsonEnvironmentConfig(pathname, collectionPath)) {
|
|
||||||
try {
|
|
||||||
const dirname = path.dirname(pathname);
|
|
||||||
const bruContent = fs.readFileSync(pathname, 'utf8');
|
|
||||||
|
|
||||||
const jsonData = JSON.parse(bruContent);
|
|
||||||
|
|
||||||
const envDirectory = path.join(dirname, 'environments');
|
|
||||||
if (!fs.existsSync(envDirectory)) {
|
|
||||||
fs.mkdirSync(envDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const env of jsonData) {
|
|
||||||
const bruEnvFilename = path.join(envDirectory, `${env.name}.bru`);
|
|
||||||
const bruContent = envJsonToBru(env);
|
|
||||||
await writeFile(bruEnvFilename, bruContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fs.unlinkSync(pathname);
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isBruEnvironmentConfig(pathname, collectionPath)) {
|
if (isBruEnvironmentConfig(pathname, collectionPath)) {
|
||||||
return addEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
return addEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrate old json files to bru
|
if (isCollectionRootBruFile(pathname, collectionPath)) {
|
||||||
if (hasJsonExtension(pathname)) {
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
collectionRoot: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const json = fs.readFileSync(pathname, 'utf8');
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
const jsonData = JSON.parse(json);
|
|
||||||
|
|
||||||
await itemSchema.validate(jsonData);
|
file.data = collectionBruToJson(bruContent);
|
||||||
|
|
||||||
const content = jsonToBru(jsonData);
|
hydrateBruCollectionFileWithUuid(file.data);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||||
const re = /(.*)\.json$/;
|
return;
|
||||||
const subst = `$1.bru`;
|
|
||||||
const bruFilename = pathname.replace(re, subst);
|
|
||||||
|
|
||||||
await writeFile(bruFilename, content);
|
|
||||||
await fs.unlinkSync(pathname);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// do nothing
|
console.error(err);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -268,12 +252,8 @@ const add = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
try {
|
try {
|
||||||
let bruContent = fs.readFileSync(pathname, 'utf8');
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
// migrate old bru format to new bru format
|
|
||||||
if (isLegacyBruFile(bruContent)) {
|
|
||||||
bruContent = await migrateLegacyBruFile(bruContent, pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.data = bruToJson(bruContent);
|
file.data = bruToJson(bruContent);
|
||||||
|
|
||||||
hydrateRequestWithUuid(file.data, pathname);
|
hydrateRequestWithUuid(file.data, pathname);
|
||||||
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
win.webContents.send('main:collection-tree-updated', 'addFile', file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@ -340,6 +320,30 @@ const change = async (win, pathname, collectionUid, collectionPath) => {
|
|||||||
return changeEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
return changeEnvironmentFile(win, pathname, collectionUid, collectionPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isCollectionRootBruFile(pathname, collectionPath)) {
|
||||||
|
const file = {
|
||||||
|
meta: {
|
||||||
|
collectionUid,
|
||||||
|
pathname,
|
||||||
|
name: path.basename(pathname),
|
||||||
|
collectionRoot: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
let bruContent = fs.readFileSync(pathname, 'utf8');
|
||||||
|
|
||||||
|
file.data = collectionBruToJson(bruContent);
|
||||||
|
|
||||||
|
hydrateBruCollectionFileWithUuid(file.data);
|
||||||
|
win.webContents.send('main:collection-tree-updated', 'change', file);
|
||||||
|
return;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (hasBruExtension(pathname)) {
|
if (hasBruExtension(pathname)) {
|
||||||
try {
|
try {
|
||||||
const file = {
|
const file = {
|
||||||
@ -404,11 +408,6 @@ class Watcher {
|
|||||||
this.watchers[watchPath].close();
|
this.watchers[watchPath].close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo
|
|
||||||
// enable this in a future release
|
|
||||||
// once we can confirm all older json based files have been auto migrated to .bru format
|
|
||||||
// watchPath = path.join(watchPath, '**/*.bru');
|
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const watcher = chokidar.watch(watchPath, {
|
const watcher = chokidar.watch(watchPath, {
|
||||||
|
@ -1,6 +1,56 @@
|
|||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { bruToJsonV2, jsonToBruV2, bruToEnvJsonV2, envJsonToBruV2 } = require('@usebruno/lang');
|
const {
|
||||||
const { each } = require('lodash');
|
bruToJsonV2,
|
||||||
|
jsonToBruV2,
|
||||||
|
bruToEnvJsonV2,
|
||||||
|
envJsonToBruV2,
|
||||||
|
collectionBruToJson: _collectionBruToJson,
|
||||||
|
jsonToCollectionBru: _jsonToCollectionBru
|
||||||
|
} = require('@usebruno/lang');
|
||||||
|
|
||||||
|
const collectionBruToJson = (bru) => {
|
||||||
|
try {
|
||||||
|
const json = _collectionBruToJson(bru);
|
||||||
|
|
||||||
|
const transformedJson = {
|
||||||
|
request: {
|
||||||
|
params: _.get(json, 'query', []),
|
||||||
|
headers: _.get(json, 'headers', []),
|
||||||
|
auth: _.get(json, 'auth', {}),
|
||||||
|
script: _.get(json, 'script', {}),
|
||||||
|
vars: _.get(json, 'vars', {}),
|
||||||
|
tests: _.get(json, 'tests', '')
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return transformedJson;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonToCollectionBru = (json) => {
|
||||||
|
try {
|
||||||
|
const collectionBruJson = {
|
||||||
|
query: _.get(json, 'request.params', []),
|
||||||
|
headers: _.get(json, 'request.headers', []),
|
||||||
|
auth: _.get(json, 'request.auth', {}),
|
||||||
|
script: {
|
||||||
|
req: _.get(json, 'request.script.req', ''),
|
||||||
|
res: _.get(json, 'request.script.res', '')
|
||||||
|
},
|
||||||
|
vars: {
|
||||||
|
req: _.get(json, 'request.vars.req', []),
|
||||||
|
res: _.get(json, 'request.vars.req', [])
|
||||||
|
},
|
||||||
|
tests: _.get(json, 'request.tests', '')
|
||||||
|
};
|
||||||
|
|
||||||
|
return _jsonToCollectionBru(collectionBruJson);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const bruToEnvJson = (bru) => {
|
const bruToEnvJson = (bru) => {
|
||||||
try {
|
try {
|
||||||
@ -10,7 +60,7 @@ const bruToEnvJson = (bru) => {
|
|||||||
// this need to be evaluated and safely removed
|
// this need to be evaluated and safely removed
|
||||||
// i don't see it being used in schema validation
|
// i don't see it being used in schema validation
|
||||||
if (json && json.variables && json.variables.length) {
|
if (json && json.variables && json.variables.length) {
|
||||||
each(json.variables, (v) => (v.type = 'text'));
|
_.each(json.variables, (v) => (v.type = 'text'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return json;
|
return json;
|
||||||
@ -129,5 +179,7 @@ module.exports = {
|
|||||||
bruToJson,
|
bruToJson,
|
||||||
jsonToBru,
|
jsonToBru,
|
||||||
bruToEnvJson,
|
bruToEnvJson,
|
||||||
envJsonToBru
|
envJsonToBru,
|
||||||
|
collectionBruToJson,
|
||||||
|
jsonToCollectionBru
|
||||||
};
|
};
|
||||||
|
@ -1,99 +0,0 @@
|
|||||||
const {
|
|
||||||
bruToEnvJson: bruToEnvJsonV1,
|
|
||||||
bruToJson: bruToJsonV1,
|
|
||||||
|
|
||||||
jsonToBruV2,
|
|
||||||
envJsonToBruV2
|
|
||||||
} = require('@usebruno/lang');
|
|
||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
const { writeFile } = require('../utils/filesystem');
|
|
||||||
|
|
||||||
const isLegacyEnvFile = (bruContent = '') => {
|
|
||||||
bruContent = bruContent.trim();
|
|
||||||
let regex = /^vars[\s\S]*\/vars$/;
|
|
||||||
|
|
||||||
return regex.test(bruContent);
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrateLegacyEnvFile = async (bruContent, pathname) => {
|
|
||||||
const envJson = bruToEnvJsonV1(bruContent);
|
|
||||||
const newBruContent = envJsonToBruV2(envJson);
|
|
||||||
|
|
||||||
await writeFile(pathname, newBruContent);
|
|
||||||
|
|
||||||
return newBruContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
const isLegacyBruFile = (bruContent = '') => {
|
|
||||||
bruContent = bruContent.trim();
|
|
||||||
let lines = bruContent.split(/\r?\n/);
|
|
||||||
let hasName = false;
|
|
||||||
let hasMethod = false;
|
|
||||||
let hasUrl = false;
|
|
||||||
|
|
||||||
for (let line of lines) {
|
|
||||||
line = line.trim();
|
|
||||||
if (line.startsWith('name')) {
|
|
||||||
hasName = true;
|
|
||||||
} else if (line.startsWith('method')) {
|
|
||||||
hasMethod = true;
|
|
||||||
} else if (line.startsWith('url')) {
|
|
||||||
hasUrl = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return hasName && hasMethod && hasUrl;
|
|
||||||
};
|
|
||||||
|
|
||||||
const migrateLegacyBruFile = async (bruContent, pathname) => {
|
|
||||||
const json = bruToJsonV1(bruContent);
|
|
||||||
|
|
||||||
let type = _.get(json, 'type');
|
|
||||||
if (type === 'http-request') {
|
|
||||||
type = 'http';
|
|
||||||
} else if (type === 'graphql-request') {
|
|
||||||
type = 'graphql';
|
|
||||||
} else {
|
|
||||||
type = 'http';
|
|
||||||
}
|
|
||||||
|
|
||||||
let script = {};
|
|
||||||
let legacyScript = _.get(json, 'request.script');
|
|
||||||
if (legacyScript && legacyScript.trim().length > 0) {
|
|
||||||
script = {
|
|
||||||
res: legacyScript
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const bruJson = {
|
|
||||||
meta: {
|
|
||||||
name: _.get(json, 'name'),
|
|
||||||
type: type,
|
|
||||||
seq: _.get(json, 'seq')
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
method: _.lowerCase(_.get(json, 'request.method')),
|
|
||||||
url: _.get(json, 'request.url'),
|
|
||||||
body: _.get(json, 'request.body.mode', 'none')
|
|
||||||
},
|
|
||||||
query: _.get(json, 'request.params', []),
|
|
||||||
headers: _.get(json, 'request.headers', []),
|
|
||||||
body: _.get(json, 'request.body', {}),
|
|
||||||
script: script,
|
|
||||||
tests: _.get(json, 'request.tests', '')
|
|
||||||
};
|
|
||||||
|
|
||||||
const newBruContent = jsonToBruV2(bruJson);
|
|
||||||
|
|
||||||
await writeFile(pathname, newBruContent);
|
|
||||||
|
|
||||||
return newBruContent;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
isLegacyEnvFile,
|
|
||||||
migrateLegacyEnvFile,
|
|
||||||
isLegacyBruFile,
|
|
||||||
migrateLegacyBruFile
|
|
||||||
};
|
|
@ -9,15 +9,20 @@ const LastOpenedCollections = require('./store/last-opened-collections');
|
|||||||
const registerNetworkIpc = require('./ipc/network');
|
const registerNetworkIpc = require('./ipc/network');
|
||||||
const registerCollectionsIpc = require('./ipc/collection');
|
const registerCollectionsIpc = require('./ipc/collection');
|
||||||
const Watcher = require('./app/watcher');
|
const Watcher = require('./app/watcher');
|
||||||
|
const { loadWindowState, saveWindowState } = require('./utils/window');
|
||||||
|
|
||||||
const lastOpenedCollections = new LastOpenedCollections();
|
const lastOpenedCollections = new LastOpenedCollections();
|
||||||
|
|
||||||
setContentSecurityPolicy(`
|
const contentSecurityPolicy = [
|
||||||
default-src * 'unsafe-inline' 'unsafe-eval';
|
isDev ? "default-src 'self' 'unsafe-inline' 'unsafe-eval'" : "default-src 'self'",
|
||||||
script-src * 'unsafe-inline' 'unsafe-eval';
|
"connect-src 'self' https://api.github.com/repos/usebruno/bruno",
|
||||||
connect-src * 'unsafe-inline';
|
"font-src 'self' https://fonts.gstatic.com",
|
||||||
form-action 'none';
|
"form-action 'none'",
|
||||||
`);
|
"img-src 'self' blob: data:",
|
||||||
|
"style-src 'self' https://fonts.googleapis.com"
|
||||||
|
];
|
||||||
|
|
||||||
|
setContentSecurityPolicy(contentSecurityPolicy.join(';'));
|
||||||
|
|
||||||
const menu = Menu.buildFromTemplate(menuTemplate);
|
const menu = Menu.buildFromTemplate(menuTemplate);
|
||||||
Menu.setApplicationMenu(menu);
|
Menu.setApplicationMenu(menu);
|
||||||
@ -27,9 +32,13 @@ let watcher;
|
|||||||
|
|
||||||
// Prepare the renderer once the app is ready
|
// Prepare the renderer once the app is ready
|
||||||
app.on('ready', async () => {
|
app.on('ready', async () => {
|
||||||
|
const { x, y, width, height } = loadWindowState();
|
||||||
|
|
||||||
mainWindow = new BrowserWindow({
|
mainWindow = new BrowserWindow({
|
||||||
width: 1280,
|
x,
|
||||||
height: 768,
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
@ -37,8 +46,10 @@ app.on('ready', async () => {
|
|||||||
webviewTag: true
|
webviewTag: true
|
||||||
},
|
},
|
||||||
title: 'Bruno',
|
title: 'Bruno',
|
||||||
icon: path.join(__dirname, 'about/256x256.png'),
|
icon: path.join(__dirname, 'about/256x256.png')
|
||||||
autoHideMenuBar: true
|
// we will bring this back
|
||||||
|
// see https://github.com/usebruno/bruno/issues/440
|
||||||
|
// autoHideMenuBar: true
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = isDev
|
const url = isDev
|
||||||
@ -52,6 +63,9 @@ app.on('ready', async () => {
|
|||||||
mainWindow.loadURL(url);
|
mainWindow.loadURL(url);
|
||||||
watcher = new Watcher();
|
watcher = new Watcher();
|
||||||
|
|
||||||
|
mainWindow.on('resize', () => saveWindowState(mainWindow));
|
||||||
|
mainWindow.on('move', () => saveWindowState(mainWindow));
|
||||||
|
|
||||||
mainWindow.webContents.on('new-window', function (e, url) {
|
mainWindow.webContents.on('new-window', function (e, url) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
require('electron').shell.openExternal(url);
|
require('electron').shell.openExternal(url);
|
||||||
|
@ -2,7 +2,7 @@ const _ = require('lodash');
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { ipcMain, shell } = require('electron');
|
const { ipcMain, shell } = require('electron');
|
||||||
const { envJsonToBru, bruToJson, jsonToBru } = require('../bru');
|
const { envJsonToBru, bruToJson, jsonToBru, jsonToCollectionBru } = require('../bru');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
isValidPathname,
|
isValidPathname,
|
||||||
@ -101,6 +101,17 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:save-collection-root', async (event, collectionPathname, collectionRoot) => {
|
||||||
|
try {
|
||||||
|
const collectionBruFilePath = path.join(collectionPathname, 'collection.bru');
|
||||||
|
|
||||||
|
const content = jsonToCollectionBru(collectionRoot);
|
||||||
|
await writeFile(collectionBruFilePath, content);
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// new request
|
// new request
|
||||||
ipcMain.handle('renderer:new-request', async (event, pathname, request) => {
|
ipcMain.handle('renderer:new-request', async (event, pathname, request) => {
|
||||||
try {
|
try {
|
||||||
@ -486,6 +497,10 @@ const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedCollection
|
|||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('renderer:open-devtools', async () => {
|
||||||
|
mainWindow.webContents.openDevTools();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
const registerMainEventHandlers = (mainWindow, watcher, lastOpenedCollections) => {
|
||||||
|
@ -23,9 +23,11 @@ function makeAxiosInstance() {
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
const end = Date.now();
|
if (error.response) {
|
||||||
const start = error.config.headers['request-start-time'];
|
const end = Date.now();
|
||||||
error.response.headers['request-duration'] = end - start;
|
const start = error.config.headers['request-start-time'];
|
||||||
|
error.response.headers['request-duration'] = end - start;
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const os = require('os');
|
||||||
const qs = require('qs');
|
const qs = require('qs');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
@ -5,19 +6,21 @@ const decomment = require('decomment');
|
|||||||
const Mustache = require('mustache');
|
const Mustache = require('mustache');
|
||||||
const FormData = require('form-data');
|
const FormData = require('form-data');
|
||||||
const { ipcMain } = require('electron');
|
const { ipcMain } = require('electron');
|
||||||
const { forOwn, extend, each, get } = require('lodash');
|
const { forOwn, extend, each, get, compact } = require('lodash');
|
||||||
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
const { VarsRuntime, AssertRuntime, ScriptRuntime, TestRuntime } = require('@usebruno/js');
|
||||||
const prepareRequest = require('./prepare-request');
|
const prepareRequest = require('./prepare-request');
|
||||||
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
const prepareGqlIntrospectionRequest = require('./prepare-gql-introspection-request');
|
||||||
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
|
const { cancelTokens, saveCancelToken, deleteCancelToken } = require('../../utils/cancel-token');
|
||||||
const { uuid } = require('../../utils/common');
|
const { uuid } = require('../../utils/common');
|
||||||
const interpolateVars = require('./interpolate-vars');
|
const interpolateVars = require('./interpolate-vars');
|
||||||
|
const { interpolateString } = require('./interpolate-string');
|
||||||
const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper');
|
const { sortFolder, getAllRequestsInFolderRecursively } = require('./helper');
|
||||||
const { getPreferences } = require('../../store/preferences');
|
const { getPreferences } = require('../../store/preferences');
|
||||||
const { getProcessEnvVars } = require('../../store/process-env');
|
const { getProcessEnvVars } = require('../../store/process-env');
|
||||||
const { getBrunoConfig } = require('../../store/bruno-config');
|
const { getBrunoConfig } = require('../../store/bruno-config');
|
||||||
const { HttpsProxyAgent } = require('https-proxy-agent');
|
const { HttpsProxyAgent } = require('https-proxy-agent');
|
||||||
const { HttpProxyAgent } = require('http-proxy-agent');
|
const { HttpProxyAgent } = require('http-proxy-agent');
|
||||||
|
const { SocksProxyAgent } = require('socks-proxy-agent');
|
||||||
const { makeAxiosInstance } = require('./axios-instance');
|
const { makeAxiosInstance } = require('./axios-instance');
|
||||||
const { addAwsV4Interceptor, resolveCredentials } = require('./awsv4auth-helper');
|
const { addAwsV4Interceptor, resolveCredentials } = require('./awsv4auth-helper');
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ const getSize = (data) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof data === 'object') {
|
if (typeof data === 'object') {
|
||||||
return Buffer.byteLength(JSON.stringify(data), 'utf8');
|
return Buffer.byteLength(safeStringifyJSON(data), 'utf8');
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -81,224 +84,217 @@ const getSize = (data) => {
|
|||||||
|
|
||||||
const registerNetworkIpc = (mainWindow) => {
|
const registerNetworkIpc = (mainWindow) => {
|
||||||
// handler for sending http request
|
// handler for sending http request
|
||||||
ipcMain.handle(
|
ipcMain.handle('send-http-request', async (event, item, collection, environment, collectionVariables) => {
|
||||||
'send-http-request',
|
const collectionUid = collection.uid;
|
||||||
async (event, item, collectionUid, collectionPath, environment, collectionVariables) => {
|
const collectionPath = collection.pathname;
|
||||||
const cancelTokenUid = uuid();
|
const cancelTokenUid = uuid();
|
||||||
const requestUid = uuid();
|
const requestUid = uuid();
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
console[type](...args);
|
console[type](...args);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:console-log', {
|
mainWindow.webContents.send('main:console-log', {
|
||||||
type,
|
type,
|
||||||
args
|
args
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
|
type: 'request-queued',
|
||||||
|
requestUid,
|
||||||
|
collectionUid,
|
||||||
|
itemUid: item.uid,
|
||||||
|
cancelTokenUid
|
||||||
|
});
|
||||||
|
|
||||||
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
const _request = item.draft ? item.draft.request : item.request;
|
||||||
|
const request = prepareRequest(_request, collectionRoot);
|
||||||
|
const envVars = getEnvVars(environment);
|
||||||
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// make axios work in node using form data
|
||||||
|
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
||||||
|
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
||||||
|
const form = new FormData();
|
||||||
|
forOwn(request.data, (value, key) => {
|
||||||
|
form.append(key, value);
|
||||||
});
|
});
|
||||||
};
|
extend(request.headers, form.getHeaders());
|
||||||
|
request.data = form;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cancelToken = axios.CancelToken.source();
|
||||||
|
request.cancelToken = cancelToken.token;
|
||||||
|
saveCancelToken(cancelTokenUid, cancelToken);
|
||||||
|
|
||||||
|
// run pre-request vars
|
||||||
|
const preRequestVars = get(request, 'vars.req', []);
|
||||||
|
if (preRequestVars && preRequestVars.length) {
|
||||||
|
const varsRuntime = new VarsRuntime();
|
||||||
|
const result = varsRuntime.runPreRequestVars(
|
||||||
|
preRequestVars,
|
||||||
|
request,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
processEnvVars
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: result.envVariables,
|
||||||
|
collectionVariables: result.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run pre-request script
|
||||||
|
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
|
if (requestScript && requestScript.length) {
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = await scriptRuntime.runRequestScript(
|
||||||
|
decomment(requestScript),
|
||||||
|
request,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: result.envVariables,
|
||||||
|
collectionVariables: result.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interpolateVars(request, envVars, collectionVariables, processEnvVars);
|
||||||
|
|
||||||
|
// stringify the request url encoded params
|
||||||
|
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
||||||
|
request.data = qs.stringify(request.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo:
|
||||||
|
// i have no clue why electron can't send the request object
|
||||||
|
// without safeParseJSON(safeStringifyJSON(request.data))
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
type: 'request-queued',
|
type: 'request-sent',
|
||||||
requestUid,
|
requestSent: {
|
||||||
|
url: request.url,
|
||||||
|
method: request.method,
|
||||||
|
headers: request.headers,
|
||||||
|
data: safeParseJSON(safeStringifyJSON(request.data))
|
||||||
|
},
|
||||||
collectionUid,
|
collectionUid,
|
||||||
itemUid: item.uid,
|
itemUid: item.uid,
|
||||||
|
requestUid,
|
||||||
cancelTokenUid
|
cancelTokenUid
|
||||||
});
|
});
|
||||||
|
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const preferences = getPreferences();
|
||||||
const request = prepareRequest(_request);
|
const sslVerification = get(preferences, 'request.sslVerification', true);
|
||||||
const envVars = getEnvVars(environment);
|
const httpsAgentRequestFields = {};
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
if (!sslVerification) {
|
||||||
|
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
||||||
|
} else {
|
||||||
|
const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
|
||||||
|
cacertFile = cacertArray.find((el) => el);
|
||||||
|
if (cacertFile && cacertFile.length > 1) {
|
||||||
|
try {
|
||||||
|
const fs = require('fs');
|
||||||
|
caCrt = fs.readFileSync(cacertFile);
|
||||||
|
httpsAgentRequestFields['ca'] = caCrt;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('Error reading CA cert file:' + cacertFile, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// proxy configuration
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const allowScriptFilesystemAccess = get(brunoConfig, 'filesystemAccess.allow', false);
|
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||||
|
if (proxyEnabled) {
|
||||||
|
let proxyUri;
|
||||||
|
|
||||||
try {
|
const interpolationOptions = {
|
||||||
// make axios work in node using form data
|
envVars,
|
||||||
// reference: https://github.com/axios/axios/issues/1006#issuecomment-320165427
|
collectionVariables,
|
||||||
if (request.headers && request.headers['content-type'] === 'multipart/form-data') {
|
processEnvVars
|
||||||
const form = new FormData();
|
};
|
||||||
forOwn(request.data, (value, key) => {
|
|
||||||
form.append(key, value);
|
|
||||||
});
|
|
||||||
extend(request.headers, form.getHeaders());
|
|
||||||
request.data = form;
|
|
||||||
}
|
|
||||||
|
|
||||||
const cancelToken = axios.CancelToken.source();
|
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
||||||
request.cancelToken = cancelToken.token;
|
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
||||||
saveCancelToken(cancelTokenUid, cancelToken);
|
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
|
||||||
|
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||||
|
const socksEnabled = proxyProtocol.includes('socks');
|
||||||
|
|
||||||
// run pre-request vars
|
if (proxyAuthEnabled) {
|
||||||
const preRequestVars = get(request, 'vars.req', []);
|
const proxyAuthUsername = interpolateString(get(brunoConfig, 'proxy.auth.username'), interpolationOptions);
|
||||||
if (preRequestVars && preRequestVars.length) {
|
const proxyAuthPassword = interpolateString(get(brunoConfig, 'proxy.auth.password'), interpolationOptions);
|
||||||
const varsRuntime = new VarsRuntime();
|
|
||||||
const result = varsRuntime.runPreRequestVars(
|
|
||||||
preRequestVars,
|
|
||||||
request,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
processEnvVars
|
|
||||||
);
|
|
||||||
|
|
||||||
if (result) {
|
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run pre-request script
|
|
||||||
const requestScript = get(request, 'script.req');
|
|
||||||
if (requestScript && requestScript.length) {
|
|
||||||
const scriptRuntime = new ScriptRuntime();
|
|
||||||
const result = await scriptRuntime.runRequestScript(
|
|
||||||
decomment(requestScript),
|
|
||||||
request,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
allowScriptFilesystemAccess
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
interpolateVars(request, envVars, collectionVariables, processEnvVars);
|
|
||||||
|
|
||||||
// stringify the request url encoded params
|
|
||||||
if (request.headers['content-type'] === 'application/x-www-form-urlencoded') {
|
|
||||||
request.data = qs.stringify(request.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo:
|
|
||||||
// i have no clue why electron can't send the request object
|
|
||||||
// without safeParseJSON(safeStringifyJSON(request.data))
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'request-sent',
|
|
||||||
requestSent: {
|
|
||||||
url: request.url,
|
|
||||||
method: request.method,
|
|
||||||
headers: request.headers,
|
|
||||||
data: safeParseJSON(safeStringifyJSON(request.data))
|
|
||||||
},
|
|
||||||
collectionUid,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
cancelTokenUid
|
|
||||||
});
|
|
||||||
|
|
||||||
const preferences = getPreferences();
|
|
||||||
const sslVerification = get(preferences, 'request.sslVerification', true);
|
|
||||||
const httpsAgentRequestFields = {};
|
|
||||||
if (!sslVerification) {
|
|
||||||
httpsAgentRequestFields['rejectUnauthorized'] = false;
|
|
||||||
} else {
|
} else {
|
||||||
const cacertArray = [preferences['cacert'], process.env.SSL_CERT_FILE, process.env.NODE_EXTRA_CA_CERTS];
|
proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
||||||
cacertFile = cacertArray.find((el) => el);
|
|
||||||
if (cacertFile && cacertFile.length > 1) {
|
|
||||||
try {
|
|
||||||
const fs = require('fs');
|
|
||||||
caCrt = fs.readFileSync(cacertFile);
|
|
||||||
httpsAgentRequestFields['ca'] = caCrt;
|
|
||||||
} catch (err) {
|
|
||||||
console.log('Error reading CA cert file:' + cacertFile, err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// proxy configuration
|
if (socksEnabled) {
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const socksProxyAgent = new SocksProxyAgent(proxyUri);
|
||||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
|
||||||
if (proxyEnabled) {
|
|
||||||
const proxyProtocol = get(brunoConfig, 'proxy.protocol');
|
|
||||||
const proxyHostname = get(brunoConfig, 'proxy.hostname');
|
|
||||||
const proxyPort = get(brunoConfig, 'proxy.port');
|
|
||||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
|
||||||
|
|
||||||
let proxy;
|
request.httpsAgent = socksProxyAgent;
|
||||||
|
|
||||||
if (proxyAuthEnabled) {
|
|
||||||
const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username');
|
|
||||||
const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password');
|
|
||||||
|
|
||||||
proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
|
||||||
} else {
|
|
||||||
proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
request.httpAgent = socksProxyAgent;
|
||||||
|
} else {
|
||||||
request.httpsAgent = new HttpsProxyAgent(
|
request.httpsAgent = new HttpsProxyAgent(
|
||||||
proxy,
|
proxyUri,
|
||||||
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
Object.keys(httpsAgentRequestFields).length > 0 ? { ...httpsAgentRequestFields } : undefined
|
||||||
);
|
);
|
||||||
|
|
||||||
request.httpAgent = new HttpProxyAgent(proxy);
|
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||||
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
|
||||||
request.httpsAgent = new https.Agent({
|
|
||||||
...httpsAgentRequestFields
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
} else if (Object.keys(httpsAgentRequestFields).length > 0) {
|
||||||
|
request.httpsAgent = new https.Agent({
|
||||||
|
...httpsAgentRequestFields
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const axiosInstance = makeAxiosInstance();
|
const axiosInstance = makeAxiosInstance();
|
||||||
|
|
||||||
if (request.awsv4config) {
|
if (request.awsv4config) {
|
||||||
request.awsv4config = await resolveCredentials(request);
|
request.awsv4config = await resolveCredentials(request);
|
||||||
addAwsV4Interceptor(axiosInstance, request);
|
addAwsV4Interceptor(axiosInstance, request);
|
||||||
delete request.awsv4config;
|
delete request.awsv4config;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type {import('axios').AxiosResponse} */
|
/** @type {import('axios').AxiosResponse} */
|
||||||
const response = await axiosInstance(request);
|
const response = await axiosInstance(request);
|
||||||
|
|
||||||
// run post-response vars
|
// run post-response vars
|
||||||
const postResponseVars = get(request, 'vars.res', []);
|
const postResponseVars = get(request, 'vars.res', []);
|
||||||
if (postResponseVars && postResponseVars.length) {
|
if (postResponseVars && postResponseVars.length) {
|
||||||
const varsRuntime = new VarsRuntime();
|
const varsRuntime = new VarsRuntime();
|
||||||
const result = varsRuntime.runPostResponseVars(
|
const result = varsRuntime.runPostResponseVars(
|
||||||
postResponseVars,
|
postResponseVars,
|
||||||
request,
|
request,
|
||||||
response,
|
response,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
processEnvVars
|
processEnvVars
|
||||||
);
|
);
|
||||||
|
|
||||||
if (result) {
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: result.envVariables,
|
|
||||||
collectionVariables: result.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// run post-response script
|
|
||||||
const responseScript = get(request, 'script.res');
|
|
||||||
if (responseScript && responseScript.length) {
|
|
||||||
const scriptRuntime = new ScriptRuntime();
|
|
||||||
const result = await scriptRuntime.runResponseScript(
|
|
||||||
decomment(responseScript),
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
allowScriptFilesystemAccess
|
|
||||||
);
|
|
||||||
|
|
||||||
|
if (result) {
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
envVariables: result.envVariables,
|
envVariables: result.envVariables,
|
||||||
collectionVariables: result.collectionVariables,
|
collectionVariables: result.collectionVariables,
|
||||||
@ -306,7 +302,116 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionUid
|
collectionUid
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// run post-response script
|
||||||
|
const responseScript = compact([get(collectionRoot, 'request.script.res'), get(request, 'script.res')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
|
if (responseScript && responseScript.length) {
|
||||||
|
const scriptRuntime = new ScriptRuntime();
|
||||||
|
const result = await scriptRuntime.runResponseScript(
|
||||||
|
decomment(responseScript),
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: result.envVariables,
|
||||||
|
collectionVariables: result.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// run assertions
|
||||||
|
const assertions = get(request, 'assertions');
|
||||||
|
if (assertions) {
|
||||||
|
const assertRuntime = new AssertRuntime();
|
||||||
|
const results = assertRuntime.runAssertions(
|
||||||
|
assertions,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
|
type: 'assertion-results',
|
||||||
|
results: results,
|
||||||
|
itemUid: item.uid,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// run tests
|
||||||
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
|
if (typeof testFile === 'string') {
|
||||||
|
const testRuntime = new TestRuntime();
|
||||||
|
const testResults = await testRuntime.runTests(
|
||||||
|
decomment(testFile),
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
envVars,
|
||||||
|
collectionVariables,
|
||||||
|
collectionPath,
|
||||||
|
onConsoleLog,
|
||||||
|
processEnvVars,
|
||||||
|
scriptingConfig
|
||||||
|
);
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
|
type: 'test-results',
|
||||||
|
results: testResults.results,
|
||||||
|
itemUid: item.uid,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
|
||||||
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
|
envVariables: testResults.envVariables,
|
||||||
|
collectionVariables: testResults.collectionVariables,
|
||||||
|
requestUid,
|
||||||
|
collectionUid
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCancelToken(cancelTokenUid);
|
||||||
|
// Prevents the duration on leaking to the actual result
|
||||||
|
const requestDuration = response.headers.get('request-duration');
|
||||||
|
response.headers.delete('request-duration');
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: response.status,
|
||||||
|
statusText: response.statusText,
|
||||||
|
headers: response.headers,
|
||||||
|
data: response.data,
|
||||||
|
duration: requestDuration
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
// todo: better error handling
|
||||||
|
// need to convey the error to the UI
|
||||||
|
// and need not be always a network error
|
||||||
|
deleteCancelToken(cancelTokenUid);
|
||||||
|
|
||||||
|
if (axios.isCancel(error)) {
|
||||||
|
let error = new Error('Request cancelled');
|
||||||
|
error.isCancel = true;
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error && error.response) {
|
||||||
// run assertions
|
// run assertions
|
||||||
const assertions = get(request, 'assertions');
|
const assertions = get(request, 'assertions');
|
||||||
if (assertions) {
|
if (assertions) {
|
||||||
@ -314,7 +419,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const results = assertRuntime.runAssertions(
|
const results = assertRuntime.runAssertions(
|
||||||
assertions,
|
assertions,
|
||||||
request,
|
request,
|
||||||
response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath
|
collectionPath
|
||||||
@ -330,19 +435,22 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
decomment(testFile),
|
decomment(testFile),
|
||||||
request,
|
request,
|
||||||
response,
|
error.response,
|
||||||
envVars,
|
envVars,
|
||||||
collectionVariables,
|
collectionVariables,
|
||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
mainWindow.webContents.send('main:run-request-event', {
|
||||||
@ -361,101 +469,21 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
// Prevents the duration from leaking to the actual result
|
||||||
// Prevents the duration on leaking to the actual result
|
const requestDuration = error.response.headers.get('request-duration');
|
||||||
const requestDuration = response.headers.get('request-duration');
|
error.response.headers.delete('request-duration');
|
||||||
response.headers.delete('request-duration');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: error.response.status,
|
||||||
statusText: response.statusText,
|
statusText: error.response.statusText,
|
||||||
headers: response.headers,
|
headers: error.response.headers,
|
||||||
data: response.data,
|
data: error.response.data,
|
||||||
duration: requestDuration
|
duration: requestDuration ?? 0
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
// todo: better error handling
|
|
||||||
// need to convey the error to the UI
|
|
||||||
// and need not be always a network error
|
|
||||||
deleteCancelToken(cancelTokenUid);
|
|
||||||
|
|
||||||
if (axios.isCancel(error)) {
|
|
||||||
let error = new Error('Request cancelled');
|
|
||||||
error.isCancel = true;
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error && error.response) {
|
|
||||||
// run assertions
|
|
||||||
const assertions = get(request, 'assertions');
|
|
||||||
if (assertions) {
|
|
||||||
const assertRuntime = new AssertRuntime();
|
|
||||||
const results = assertRuntime.runAssertions(
|
|
||||||
assertions,
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'assertion-results',
|
|
||||||
results: results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// run tests
|
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
|
||||||
if (typeof testFile === 'string') {
|
|
||||||
const testRuntime = new TestRuntime();
|
|
||||||
const testResults = await testRuntime.runTests(
|
|
||||||
decomment(testFile),
|
|
||||||
request,
|
|
||||||
error.response,
|
|
||||||
envVars,
|
|
||||||
collectionVariables,
|
|
||||||
collectionPath,
|
|
||||||
onConsoleLog,
|
|
||||||
processEnvVars,
|
|
||||||
allowScriptFilesystemAccess
|
|
||||||
);
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-request-event', {
|
|
||||||
type: 'test-results',
|
|
||||||
results: testResults.results,
|
|
||||||
itemUid: item.uid,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
|
||||||
envVariables: testResults.envVariables,
|
|
||||||
collectionVariables: testResults.collectionVariables,
|
|
||||||
requestUid,
|
|
||||||
collectionUid
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevents the duration from leaking to the actual result
|
|
||||||
const requestDuration = error.response.headers.get('request-duration');
|
|
||||||
error.response.headers.delete('request-duration');
|
|
||||||
return {
|
|
||||||
status: error.response.status,
|
|
||||||
statusText: error.response.statusText,
|
|
||||||
headers: error.response.headers,
|
|
||||||
data: error.response.data,
|
|
||||||
duration: requestDuration ?? 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
ipcMain.handle('cancel-http-request', async (event, cancelTokenUid) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -469,10 +497,11 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('fetch-gql-schema', async (event, endpoint, environment, request, collectionVariables) => {
|
ipcMain.handle('fetch-gql-schema', async (event, endpoint, environment, request, collection) => {
|
||||||
try {
|
try {
|
||||||
const envVars = getEnvVars(environment);
|
const envVars = getEnvVars(environment);
|
||||||
const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request);
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
const preparedRequest = prepareGqlIntrospectionRequest(endpoint, envVars, request, collectionRoot);
|
||||||
|
|
||||||
const preferences = getPreferences();
|
const preferences = getPreferences();
|
||||||
const sslVerification = get(preferences, 'request.sslVerification', true);
|
const sslVerification = get(preferences, 'request.sslVerification', true);
|
||||||
@ -483,7 +512,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interpolateVars(preparedRequest, envVars, collectionVariables);
|
const processEnvVars = getProcessEnvVars(collection.uid);
|
||||||
|
interpolateVars(preparedRequest, envVars, collection.collectionVariables, processEnvVars);
|
||||||
|
|
||||||
const response = await axios(preparedRequest);
|
const response = await axios(preparedRequest);
|
||||||
|
|
||||||
@ -514,7 +544,8 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const collectionPath = collection.pathname;
|
const collectionPath = collection.pathname;
|
||||||
const folderUid = folder ? folder.uid : null;
|
const folderUid = folder ? folder.uid : null;
|
||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const allowScriptFilesystemAccess = get(brunoConfig, 'filesystemAccess.allow', false);
|
const scriptingConfig = get(brunoConfig, 'scripts', {});
|
||||||
|
const collectionRoot = get(collection, 'root', {});
|
||||||
|
|
||||||
const onConsoleLog = (type, args) => {
|
const onConsoleLog = (type, args) => {
|
||||||
console[type](...args);
|
console[type](...args);
|
||||||
@ -573,7 +604,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const _request = item.draft ? item.draft.request : item.request;
|
const _request = item.draft ? item.draft.request : item.request;
|
||||||
const request = prepareRequest(_request);
|
const request = prepareRequest(_request, collectionRoot);
|
||||||
const processEnvVars = getProcessEnvVars(collectionUid);
|
const processEnvVars = getProcessEnvVars(collectionUid);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -610,7 +641,9 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run pre-request script
|
// run pre-request script
|
||||||
const requestScript = get(request, 'script.req');
|
const requestScript = compact([get(collectionRoot, 'request.script.req'), get(request, 'script.req')]).join(
|
||||||
|
os.EOL
|
||||||
|
);
|
||||||
if (requestScript && requestScript.length) {
|
if (requestScript && requestScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runRequestScript(
|
const result = await scriptRuntime.runRequestScript(
|
||||||
@ -621,7 +654,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
@ -655,27 +688,47 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
const brunoConfig = getBrunoConfig(collectionUid);
|
const brunoConfig = getBrunoConfig(collectionUid);
|
||||||
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
const proxyEnabled = get(brunoConfig, 'proxy.enabled', false);
|
||||||
if (proxyEnabled) {
|
if (proxyEnabled) {
|
||||||
const proxyProtocol = get(brunoConfig, 'proxy.protocol');
|
let proxyUri;
|
||||||
const proxyHostname = get(brunoConfig, 'proxy.hostname');
|
const interpolationOptions = {
|
||||||
const proxyPort = get(brunoConfig, 'proxy.port');
|
envVars,
|
||||||
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
collectionVariables,
|
||||||
|
processEnvVars
|
||||||
|
};
|
||||||
|
|
||||||
let proxy;
|
const proxyProtocol = interpolateString(get(brunoConfig, 'proxy.protocol'), interpolationOptions);
|
||||||
|
const proxyHostname = interpolateString(get(brunoConfig, 'proxy.hostname'), interpolationOptions);
|
||||||
|
const proxyPort = interpolateString(get(brunoConfig, 'proxy.port'), interpolationOptions);
|
||||||
|
const proxyAuthEnabled = get(brunoConfig, 'proxy.auth.enabled', false);
|
||||||
|
const socksEnabled = proxyProtocol.includes('socks');
|
||||||
|
|
||||||
if (proxyAuthEnabled) {
|
if (proxyAuthEnabled) {
|
||||||
const proxyAuthUsername = get(brunoConfig, 'proxy.auth.username');
|
const proxyAuthUsername = interpolateString(
|
||||||
const proxyAuthPassword = get(brunoConfig, 'proxy.auth.password');
|
get(brunoConfig, 'proxy.auth.username'),
|
||||||
|
interpolationOptions
|
||||||
|
);
|
||||||
|
|
||||||
proxy = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
const proxyAuthPassword = interpolateString(
|
||||||
|
get(brunoConfig, 'proxy.auth.password'),
|
||||||
|
interpolationOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
proxyUri = `${proxyProtocol}://${proxyAuthUsername}:${proxyAuthPassword}@${proxyHostname}:${proxyPort}`;
|
||||||
} else {
|
} else {
|
||||||
proxy = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
proxyUri = `${proxyProtocol}://${proxyHostname}:${proxyPort}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.httpsAgent = new HttpsProxyAgent(proxy, {
|
if (socksEnabled) {
|
||||||
rejectUnauthorized: sslVerification
|
const socksProxyAgent = new SocksProxyAgent(proxyUri);
|
||||||
});
|
|
||||||
|
|
||||||
request.httpAgent = new HttpProxyAgent(proxy);
|
request.httpsAgent = socksProxyAgent;
|
||||||
|
request.httpAgent = socksProxyAgent;
|
||||||
|
} else {
|
||||||
|
request.httpsAgent = new HttpsProxyAgent(proxyUri, {
|
||||||
|
rejectUnauthorized: sslVerification
|
||||||
|
});
|
||||||
|
|
||||||
|
request.httpAgent = new HttpProxyAgent(proxyUri);
|
||||||
|
}
|
||||||
} else if (!sslVerification) {
|
} else if (!sslVerification) {
|
||||||
request.httpsAgent = new https.Agent({
|
request.httpsAgent = new https.Agent({
|
||||||
rejectUnauthorized: false
|
rejectUnauthorized: false
|
||||||
@ -711,7 +764,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run response script
|
// run response script
|
||||||
const responseScript = get(request, 'script.res');
|
const responseScript = compact([
|
||||||
|
get(collectionRoot, 'request.script.res'),
|
||||||
|
get(request, 'script.res')
|
||||||
|
]).join(os.EOL);
|
||||||
if (responseScript && responseScript.length) {
|
if (responseScript && responseScript.length) {
|
||||||
const scriptRuntime = new ScriptRuntime();
|
const scriptRuntime = new ScriptRuntime();
|
||||||
const result = await scriptRuntime.runResponseScript(
|
const result = await scriptRuntime.runResponseScript(
|
||||||
@ -723,7 +779,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:script-environment-update', {
|
mainWindow.webContents.send('main:script-environment-update', {
|
||||||
@ -755,7 +811,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
@ -767,7 +826,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
@ -835,7 +894,10 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// run tests
|
// run tests
|
||||||
const testFile = item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests');
|
const testFile = compact([
|
||||||
|
get(collectionRoot, 'request.tests'),
|
||||||
|
item.draft ? get(item.draft, 'request.tests') : get(item, 'request.tests')
|
||||||
|
]).join(os.EOL);
|
||||||
if (typeof testFile === 'string') {
|
if (typeof testFile === 'string') {
|
||||||
const testRuntime = new TestRuntime();
|
const testRuntime = new TestRuntime();
|
||||||
const testResults = await testRuntime.runTests(
|
const testResults = await testRuntime.runTests(
|
||||||
@ -847,7 +909,7 @@ const registerNetworkIpc = (mainWindow) => {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
);
|
);
|
||||||
|
|
||||||
mainWindow.webContents.send('main:run-folder-event', {
|
mainWindow.webContents.send('main:run-folder-event', {
|
||||||
|
@ -0,0 +1,55 @@
|
|||||||
|
const Handlebars = require('handlebars');
|
||||||
|
const { forOwn, cloneDeep } = require('lodash');
|
||||||
|
|
||||||
|
const interpolateEnvVars = (str, processEnvVars) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
return template({
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const interpolateString = (str, { envVars, collectionVariables, processEnvVars }) => {
|
||||||
|
if (!str || !str.length || typeof str !== 'string') {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
processEnvVars = processEnvVars || {};
|
||||||
|
collectionVariables = collectionVariables || {};
|
||||||
|
|
||||||
|
// we clone envVars because we don't want to modify the original object
|
||||||
|
envVars = envVars ? cloneDeep(envVars) : {};
|
||||||
|
|
||||||
|
// envVars can inturn have values as {{process.env.VAR_NAME}}
|
||||||
|
// so we need to interpolate envVars first with processEnvVars
|
||||||
|
forOwn(envVars, (value, key) => {
|
||||||
|
envVars[key] = interpolateEnvVars(value, processEnvVars);
|
||||||
|
});
|
||||||
|
|
||||||
|
const template = Handlebars.compile(str, { noEscape: true });
|
||||||
|
|
||||||
|
// collectionVariables take precedence over envVars
|
||||||
|
const combinedVars = {
|
||||||
|
...envVars,
|
||||||
|
...collectionVariables,
|
||||||
|
process: {
|
||||||
|
env: {
|
||||||
|
...processEnvVars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return template(combinedVars);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
interpolateString
|
||||||
|
};
|
@ -1,45 +1,34 @@
|
|||||||
const Mustache = require('mustache');
|
const Handlebars = require('handlebars');
|
||||||
const { getIntrospectionQuery } = require('graphql');
|
const { getIntrospectionQuery } = require('graphql');
|
||||||
const { get } = require('lodash');
|
const { setAuthHeaders } = require('./prepare-request');
|
||||||
|
|
||||||
// override the default escape function to prevent escaping
|
const prepareGqlIntrospectionRequest = (endpoint, envVars, request, collectionRoot) => {
|
||||||
Mustache.escape = function (value) {
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
const prepareGqlIntrospectionRequest = (endpoint, envVars, request) => {
|
|
||||||
if (endpoint && endpoint.length) {
|
if (endpoint && endpoint.length) {
|
||||||
endpoint = Mustache.render(endpoint, envVars);
|
endpoint = Handlebars.compile(endpoint, { noEscape: true })(envVars);
|
||||||
}
|
}
|
||||||
const introspectionQuery = getIntrospectionQuery();
|
|
||||||
const queryParams = {
|
const queryParams = {
|
||||||
query: introspectionQuery
|
query: getIntrospectionQuery()
|
||||||
};
|
};
|
||||||
|
|
||||||
let axiosRequest = {
|
let axiosRequest = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: endpoint,
|
url: endpoint,
|
||||||
headers: {
|
headers: {
|
||||||
|
...mapHeaders(request.headers),
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
data: JSON.stringify(queryParams)
|
data: JSON.stringify(queryParams)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (request.auth) {
|
return setAuthHeaders(axiosRequest, request, collectionRoot);
|
||||||
if (request.auth.mode === 'basic') {
|
};
|
||||||
axiosRequest.auth = {
|
|
||||||
username: get(request, 'auth.basic.username'),
|
|
||||||
password: get(request, 'auth.basic.password')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (request.auth.mode === 'bearer') {
|
const mapHeaders = (headers) => {
|
||||||
axiosRequest.headers.authorization = `Bearer ${get(request, 'auth.bearer.token')}`;
|
const entries = headers.filter((header) => header.enabled).map(({ name, value }) => [name, value]);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return axiosRequest;
|
return Object.fromEntries(entries);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = prepareGqlIntrospectionRequest;
|
module.exports = prepareGqlIntrospectionRequest;
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
const { get, each, filter } = require('lodash');
|
const { get, each, filter } = require('lodash');
|
||||||
const decomment = require('decomment');
|
const decomment = require('decomment');
|
||||||
|
|
||||||
const prepareRequest = (request) => {
|
// Authentication
|
||||||
const headers = {};
|
// A request can override the collection auth with another auth
|
||||||
let contentTypeDefined = false;
|
// But it cannot override the collection auth with no auth
|
||||||
each(request.headers, (h) => {
|
// We will provide support for disabling the auth via scripting in the future
|
||||||
if (h.enabled) {
|
const setAuthHeaders = (axiosRequest, request, collectionRoot) => {
|
||||||
headers[h.name] = h.value;
|
const collectionAuth = get(collectionRoot, 'request.auth');
|
||||||
if (h.name.toLowerCase() === 'content-type') {
|
if (collectionAuth) {
|
||||||
contentTypeDefined = true;
|
if (collectionAuth.mode === 'basic') {
|
||||||
}
|
axiosRequest.auth = {
|
||||||
|
username: get(collectionAuth, 'basic.username'),
|
||||||
|
password: get(collectionAuth, 'basic.password')
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
let axiosRequest = {
|
if (collectionAuth.mode === 'bearer') {
|
||||||
method: request.method,
|
axiosRequest.headers['authorization'] = `Bearer ${get(collectionAuth, 'bearer.token')}`;
|
||||||
url: request.url,
|
}
|
||||||
headers: headers
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// Authentication
|
|
||||||
if (request.auth) {
|
if (request.auth) {
|
||||||
switch (request.auth.mode) {
|
switch (request.auth.mode) {
|
||||||
case 'awsv4':
|
case 'awsv4':
|
||||||
@ -44,6 +44,40 @@ const prepareRequest = (request) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return axiosRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
const prepareRequest = (request, collectionRoot) => {
|
||||||
|
const headers = {};
|
||||||
|
let contentTypeDefined = false;
|
||||||
|
|
||||||
|
// collection headers
|
||||||
|
each(get(collectionRoot, 'request.headers', []), (h) => {
|
||||||
|
if (h.enabled) {
|
||||||
|
headers[h.name] = h.value;
|
||||||
|
if (h.name.toLowerCase() === 'content-type') {
|
||||||
|
contentTypeDefined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
each(request.headers, (h) => {
|
||||||
|
if (h.enabled) {
|
||||||
|
headers[h.name] = h.value;
|
||||||
|
if (h.name.toLowerCase() === 'content-type') {
|
||||||
|
contentTypeDefined = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let axiosRequest = {
|
||||||
|
method: request.method,
|
||||||
|
url: request.url,
|
||||||
|
headers: headers
|
||||||
|
};
|
||||||
|
|
||||||
|
axiosRequest = setAuthHeaders(axiosRequest, request, collectionRoot);
|
||||||
|
|
||||||
if (request.body.mode === 'json') {
|
if (request.body.mode === 'json') {
|
||||||
if (!contentTypeDefined) {
|
if (!contentTypeDefined) {
|
||||||
axiosRequest.headers['content-type'] = 'application/json';
|
axiosRequest.headers['content-type'] = 'application/json';
|
||||||
@ -70,6 +104,13 @@ const prepareRequest = (request) => {
|
|||||||
axiosRequest.data = request.body.xml;
|
axiosRequest.data = request.body.xml;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (request.body.mode === 'sparql') {
|
||||||
|
if (!contentTypeDefined) {
|
||||||
|
axiosRequest.headers['content-type'] = 'application/sparql-query';
|
||||||
|
}
|
||||||
|
axiosRequest.data = request.body.sparql;
|
||||||
|
}
|
||||||
|
|
||||||
if (request.body.mode === 'formUrlEncoded') {
|
if (request.body.mode === 'formUrlEncoded') {
|
||||||
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
axiosRequest.headers['content-type'] = 'application/x-www-form-urlencoded';
|
||||||
const params = {};
|
const params = {};
|
||||||
@ -108,3 +149,4 @@ const prepareRequest = (request) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
module.exports = prepareRequest;
|
module.exports = prepareRequest;
|
||||||
|
module.exports.setAuthHeaders = setAuthHeaders;
|
||||||
|
31
packages/bruno-electron/src/store/window-state.js
Normal file
31
packages/bruno-electron/src/store/window-state.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const Store = require('electron-store');
|
||||||
|
|
||||||
|
const DEFAULT_WINDOW_WIDTH = 1280;
|
||||||
|
const DEFAULT_WINDOW_HEIGHT = 768;
|
||||||
|
|
||||||
|
class WindowStateStore {
|
||||||
|
constructor() {
|
||||||
|
this.store = new Store({
|
||||||
|
name: 'preferences',
|
||||||
|
clearInvalidConfig: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getBounds() {
|
||||||
|
return (
|
||||||
|
this.store.get('window-bounds') || {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: DEFAULT_WINDOW_WIDTH,
|
||||||
|
height: DEFAULT_WINDOW_HEIGHT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setBounds(bounds) {
|
||||||
|
this.store.set('window-bounds', bounds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = WindowStateStore;
|
53
packages/bruno-electron/src/utils/window.js
Normal file
53
packages/bruno-electron/src/utils/window.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
const { screen } = require('electron');
|
||||||
|
const WindowStateStore = require('../store/window-state');
|
||||||
|
|
||||||
|
const windowStateStore = new WindowStateStore();
|
||||||
|
|
||||||
|
const DEFAULT_WINDOW_WIDTH = 1280;
|
||||||
|
const DEFAULT_WINDOW_HEIGHT = 768;
|
||||||
|
|
||||||
|
const loadWindowState = () => {
|
||||||
|
const bounds = windowStateStore.getBounds();
|
||||||
|
|
||||||
|
const positionValid = isPositionValid(bounds);
|
||||||
|
const sizeValid = isSizeValid(bounds);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: bounds.x && positionValid ? bounds.x : undefined,
|
||||||
|
y: bounds.y && positionValid ? bounds.y : undefined,
|
||||||
|
width: bounds.width && sizeValid ? bounds.width : DEFAULT_WINDOW_WIDTH,
|
||||||
|
height: bounds.height && sizeValid ? bounds.height : DEFAULT_WINDOW_HEIGHT
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveWindowState = (window) => {
|
||||||
|
const bounds = window.getBounds();
|
||||||
|
|
||||||
|
windowStateStore.setBounds(bounds);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isPositionValid = (bounds) => {
|
||||||
|
const area = getArea(bounds);
|
||||||
|
|
||||||
|
return (
|
||||||
|
bounds.x >= area.x &&
|
||||||
|
bounds.y >= area.y &&
|
||||||
|
bounds.x + bounds.width <= area.x + area.width &&
|
||||||
|
bounds.y + bounds.height <= area.y + area.height
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSizeValid = (bounds) => {
|
||||||
|
const area = getArea(bounds);
|
||||||
|
|
||||||
|
return bounds.width <= area.width && bounds.height <= area.height;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getArea = (bounds) => {
|
||||||
|
return screen.getDisplayMatching(bounds).workArea;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
loadWindowState,
|
||||||
|
saveWindowState
|
||||||
|
};
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@usebruno/js",
|
"name": "@usebruno/js",
|
||||||
"version": "0.6.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
|
@ -6,6 +6,7 @@ const BrunoRequest = require('../bruno-request');
|
|||||||
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
const { evaluateJsTemplateLiteral, evaluateJsExpression, createResponseParser } = require('../utils');
|
||||||
|
|
||||||
const { expect } = chai;
|
const { expect } = chai;
|
||||||
|
chai.use(require('chai-string'));
|
||||||
chai.use(function (chai, utils) {
|
chai.use(function (chai, utils) {
|
||||||
// Custom assertion for checking if a variable is JSON
|
// Custom assertion for checking if a variable is JSON
|
||||||
chai.Assertion.addProperty('json', function () {
|
chai.Assertion.addProperty('json', function () {
|
||||||
@ -268,7 +269,7 @@ class AssertRuntime {
|
|||||||
expect(lhs).to.endWith(rhs);
|
expect(lhs).to.endWith(rhs);
|
||||||
break;
|
break;
|
||||||
case 'between':
|
case 'between':
|
||||||
const [min, max] = value.split(',');
|
const [min, max] = rhs;
|
||||||
expect(lhs).to.be.within(min, max);
|
expect(lhs).to.be.within(min, max);
|
||||||
break;
|
break;
|
||||||
case 'isEmpty':
|
case 'isEmpty':
|
||||||
|
@ -8,12 +8,14 @@ const zlib = require('zlib');
|
|||||||
const url = require('url');
|
const url = require('url');
|
||||||
const punycode = require('punycode');
|
const punycode = require('punycode');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { get } = require('lodash');
|
||||||
const Bru = require('../bru');
|
const Bru = require('../bru');
|
||||||
const BrunoRequest = require('../bruno-request');
|
const BrunoRequest = require('../bruno-request');
|
||||||
const BrunoResponse = require('../bruno-response');
|
const BrunoResponse = require('../bruno-response');
|
||||||
const { cleanJson } = require('../utils');
|
const { cleanJson } = require('../utils');
|
||||||
|
|
||||||
// Inbuilt Library Support
|
// Inbuilt Library Support
|
||||||
|
const ajv = require('ajv');
|
||||||
const atob = require('atob');
|
const atob = require('atob');
|
||||||
const btoa = require('btoa');
|
const btoa = require('btoa');
|
||||||
const lodash = require('lodash');
|
const lodash = require('lodash');
|
||||||
@ -38,10 +40,23 @@ class ScriptRuntime {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
|
|
||||||
|
const whitelistedModules = {};
|
||||||
|
|
||||||
|
for (let module of moduleWhitelist) {
|
||||||
|
try {
|
||||||
|
whitelistedModules[module] = require(module);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
bru,
|
bru,
|
||||||
@ -79,6 +94,7 @@ class ScriptRuntime {
|
|||||||
punycode,
|
punycode,
|
||||||
zlib,
|
zlib,
|
||||||
// 3rd party libs
|
// 3rd party libs
|
||||||
|
ajv,
|
||||||
atob,
|
atob,
|
||||||
btoa,
|
btoa,
|
||||||
lodash,
|
lodash,
|
||||||
@ -89,6 +105,7 @@ class ScriptRuntime {
|
|||||||
chai,
|
chai,
|
||||||
'node-fetch': fetch,
|
'node-fetch': fetch,
|
||||||
'crypto-js': CryptoJS,
|
'crypto-js': CryptoJS,
|
||||||
|
...whitelistedModules,
|
||||||
fs: allowScriptFilesystemAccess ? fs : undefined
|
fs: allowScriptFilesystemAccess ? fs : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,11 +128,24 @@ class ScriptRuntime {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const res = new BrunoResponse(response);
|
const res = new BrunoResponse(response);
|
||||||
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
|
|
||||||
|
const whitelistedModules = {};
|
||||||
|
|
||||||
|
for (let module of moduleWhitelist) {
|
||||||
|
try {
|
||||||
|
whitelistedModules[module] = require(module);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
bru,
|
bru,
|
||||||
@ -154,6 +184,7 @@ class ScriptRuntime {
|
|||||||
punycode,
|
punycode,
|
||||||
zlib,
|
zlib,
|
||||||
// 3rd party libs
|
// 3rd party libs
|
||||||
|
ajv,
|
||||||
atob,
|
atob,
|
||||||
btoa,
|
btoa,
|
||||||
lodash,
|
lodash,
|
||||||
@ -163,6 +194,7 @@ class ScriptRuntime {
|
|||||||
axios,
|
axios,
|
||||||
'node-fetch': fetch,
|
'node-fetch': fetch,
|
||||||
'crypto-js': CryptoJS,
|
'crypto-js': CryptoJS,
|
||||||
|
...whitelistedModules,
|
||||||
fs: allowScriptFilesystemAccess ? fs : undefined
|
fs: allowScriptFilesystemAccess ? fs : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ const zlib = require('zlib');
|
|||||||
const url = require('url');
|
const url = require('url');
|
||||||
const punycode = require('punycode');
|
const punycode = require('punycode');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
const { get } = require('lodash');
|
||||||
const Bru = require('../bru');
|
const Bru = require('../bru');
|
||||||
const BrunoRequest = require('../bruno-request');
|
const BrunoRequest = require('../bruno-request');
|
||||||
const BrunoResponse = require('../bruno-response');
|
const BrunoResponse = require('../bruno-response');
|
||||||
@ -17,13 +18,15 @@ const TestResults = require('../test-results');
|
|||||||
const { cleanJson } = require('../utils');
|
const { cleanJson } = require('../utils');
|
||||||
|
|
||||||
// Inbuilt Library Support
|
// Inbuilt Library Support
|
||||||
|
const ajv = require('ajv');
|
||||||
const atob = require('atob');
|
const atob = require('atob');
|
||||||
const axios = require('axios');
|
|
||||||
const btoa = require('btoa');
|
const btoa = require('btoa');
|
||||||
const lodash = require('lodash');
|
const lodash = require('lodash');
|
||||||
const moment = require('moment');
|
const moment = require('moment');
|
||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
const nanoid = require('nanoid');
|
const nanoid = require('nanoid');
|
||||||
|
const axios = require('axios');
|
||||||
|
const fetch = require('node-fetch');
|
||||||
const CryptoJS = require('crypto-js');
|
const CryptoJS = require('crypto-js');
|
||||||
|
|
||||||
class TestRuntime {
|
class TestRuntime {
|
||||||
@ -38,11 +41,24 @@ class TestRuntime {
|
|||||||
collectionPath,
|
collectionPath,
|
||||||
onConsoleLog,
|
onConsoleLog,
|
||||||
processEnvVars,
|
processEnvVars,
|
||||||
allowScriptFilesystemAccess
|
scriptingConfig
|
||||||
) {
|
) {
|
||||||
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
const bru = new Bru(envVariables, collectionVariables, processEnvVars, collectionPath);
|
||||||
const req = new BrunoRequest(request);
|
const req = new BrunoRequest(request);
|
||||||
const res = new BrunoResponse(response);
|
const res = new BrunoResponse(response);
|
||||||
|
const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
|
||||||
|
const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
|
||||||
|
|
||||||
|
const whitelistedModules = {};
|
||||||
|
|
||||||
|
for (let module of moduleWhitelist) {
|
||||||
|
try {
|
||||||
|
whitelistedModules[module] = require(module);
|
||||||
|
} catch (e) {
|
||||||
|
// Ignore
|
||||||
|
console.warn(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const __brunoTestResults = new TestResults();
|
const __brunoTestResults = new TestResults();
|
||||||
const test = Test(__brunoTestResults, chai);
|
const test = Test(__brunoTestResults, chai);
|
||||||
@ -97,15 +113,18 @@ class TestRuntime {
|
|||||||
punycode,
|
punycode,
|
||||||
zlib,
|
zlib,
|
||||||
// 3rd party libs
|
// 3rd party libs
|
||||||
atob,
|
ajv,
|
||||||
axios,
|
|
||||||
btoa,
|
btoa,
|
||||||
|
atob,
|
||||||
lodash,
|
lodash,
|
||||||
moment,
|
moment,
|
||||||
uuid,
|
uuid,
|
||||||
nanoid,
|
nanoid,
|
||||||
|
axios,
|
||||||
chai,
|
chai,
|
||||||
|
'node-fetch': fetch,
|
||||||
'crypto-js': CryptoJS,
|
'crypto-js': CryptoJS,
|
||||||
|
...whitelistedModules,
|
||||||
fs: allowScriptFilesystemAccess ? fs : undefined
|
fs: allowScriptFilesystemAccess ? fs : undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@usebruno/lang",
|
"name": "@usebruno/lang",
|
||||||
"version": "0.5.0",
|
"version": "0.8.0",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"files": [
|
"files": [
|
||||||
@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"arcsecond": "^5.0.0",
|
"arcsecond": "^5.0.0",
|
||||||
|
"dotenv": "^16.3.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"ohm-js": "^16.6.0"
|
"ohm-js": "^16.6.0"
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,23 @@
|
|||||||
const { bruToJson, jsonToBru, bruToEnvJson, envJsonToBru } = require('../v1/src');
|
|
||||||
|
|
||||||
const bruToJsonV2 = require('../v2/src/bruToJson');
|
const bruToJsonV2 = require('../v2/src/bruToJson');
|
||||||
const jsonToBruV2 = require('../v2/src/jsonToBru');
|
const jsonToBruV2 = require('../v2/src/jsonToBru');
|
||||||
const bruToEnvJsonV2 = require('../v2/src/envToJson');
|
const bruToEnvJsonV2 = require('../v2/src/envToJson');
|
||||||
const envJsonToBruV2 = require('../v2/src/jsonToEnv');
|
const envJsonToBruV2 = require('../v2/src/jsonToEnv');
|
||||||
const dotenvToJson = require('../v2/src/dotenvToJson');
|
const dotenvToJson = require('../v2/src/dotenvToJson');
|
||||||
|
|
||||||
module.exports = {
|
const collectionBruToJson = require('../v2/src/collectionBruToJson');
|
||||||
bruToJson,
|
const jsonToCollectionBru = require('../v2/src/jsonToCollectionBru');
|
||||||
jsonToBru,
|
|
||||||
bruToEnvJson,
|
|
||||||
envJsonToBru,
|
|
||||||
|
|
||||||
|
// Todo: remove V2 suffixes
|
||||||
|
// Changes will have to be made to the CLI and GUI
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
bruToJsonV2,
|
bruToJsonV2,
|
||||||
jsonToBruV2,
|
jsonToBruV2,
|
||||||
bruToEnvJsonV2,
|
bruToEnvJsonV2,
|
||||||
envJsonToBruV2,
|
envJsonToBruV2,
|
||||||
|
|
||||||
|
collectionBruToJson,
|
||||||
|
jsonToCollectionBru,
|
||||||
|
|
||||||
dotenvToJson
|
dotenvToJson
|
||||||
};
|
};
|
||||||
|
@ -23,8 +23,8 @@ const { outdentString } = require('../../v1/src/utils');
|
|||||||
*/
|
*/
|
||||||
const grammar = ohm.grammar(`Bru {
|
const grammar = ohm.grammar(`Bru {
|
||||||
BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)*
|
BruFile = (meta | http | query | headers | auths | bodies | varsandassert | script | tests | docs)*
|
||||||
auths = authawsv4 | authbasic | authbearer
|
auths = authawsv4 | authbasic | authbearer
|
||||||
bodies = bodyjson | bodytext | bodyxml | bodygraphql | bodygraphqlvars | bodyforms | body
|
bodies = bodyjson | bodytext | bodyxml | bodysparql | bodygraphql | bodygraphqlvars | bodyforms | body
|
||||||
bodyforms = bodyformurlencoded | bodymultipart
|
bodyforms = bodyformurlencoded | bodymultipart
|
||||||
|
|
||||||
nl = "\\r"? "\\n"
|
nl = "\\r"? "\\n"
|
||||||
@ -84,6 +84,7 @@ const grammar = ohm.grammar(`Bru {
|
|||||||
bodyjson = "body:json" st* "{" nl* textblock tagend
|
bodyjson = "body:json" st* "{" nl* textblock tagend
|
||||||
bodytext = "body:text" st* "{" nl* textblock tagend
|
bodytext = "body:text" st* "{" nl* textblock tagend
|
||||||
bodyxml = "body:xml" st* "{" nl* textblock tagend
|
bodyxml = "body:xml" st* "{" nl* textblock tagend
|
||||||
|
bodysparql = "body:sparql" st* "{" nl* textblock tagend
|
||||||
bodygraphql = "body:graphql" st* "{" nl* textblock tagend
|
bodygraphql = "body:graphql" st* "{" nl* textblock tagend
|
||||||
bodygraphqlvars = "body:graphql:vars" st* "{" nl* textblock tagend
|
bodygraphqlvars = "body:graphql:vars" st* "{" nl* textblock tagend
|
||||||
|
|
||||||
@ -394,6 +395,13 @@ const sem = grammar.createSemantics().addAttribute('ast', {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
bodysparql(_1, _2, _3, _4, textblock, _5) {
|
||||||
|
return {
|
||||||
|
body: {
|
||||||
|
sparql: outdentString(textblock.sourceString)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
bodygraphql(_1, _2, _3, _4, textblock, _5) {
|
bodygraphql(_1, _2, _3, _4, textblock, _5) {
|
||||||
return {
|
return {
|
||||||
body: {
|
body: {
|
||||||
|
273
packages/bruno-lang/v2/src/collectionBruToJson.js
Normal file
273
packages/bruno-lang/v2/src/collectionBruToJson.js
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
const ohm = require('ohm-js');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const { outdentString } = require('../../v1/src/utils');
|
||||||
|
|
||||||
|
const grammar = ohm.grammar(`Bru {
|
||||||
|
BruFile = (meta | query | headers | auth | auths | vars | script | tests | docs)*
|
||||||
|
auths = authbasic | authbearer
|
||||||
|
|
||||||
|
nl = "\\r"? "\\n"
|
||||||
|
st = " " | "\\t"
|
||||||
|
stnl = st | nl
|
||||||
|
tagend = nl "}"
|
||||||
|
optionalnl = ~tagend nl
|
||||||
|
keychar = ~(tagend | st | nl | ":") any
|
||||||
|
valuechar = ~(nl | tagend) any
|
||||||
|
|
||||||
|
// Dictionary Blocks
|
||||||
|
dictionary = st* "{" pairlist? tagend
|
||||||
|
pairlist = optionalnl* pair (~tagend stnl* pair)* (~tagend space)*
|
||||||
|
pair = st* key st* ":" st* value st*
|
||||||
|
key = keychar*
|
||||||
|
value = valuechar*
|
||||||
|
|
||||||
|
// Text Blocks
|
||||||
|
textblock = textline (~tagend nl textline)*
|
||||||
|
textline = textchar*
|
||||||
|
textchar = ~nl any
|
||||||
|
|
||||||
|
meta = "meta" dictionary
|
||||||
|
|
||||||
|
auth = "auth" dictionary
|
||||||
|
|
||||||
|
headers = "headers" dictionary
|
||||||
|
|
||||||
|
query = "query" dictionary
|
||||||
|
|
||||||
|
vars = varsreq | varsres
|
||||||
|
varsreq = "vars:pre-request" dictionary
|
||||||
|
varsres = "vars:post-response" dictionary
|
||||||
|
|
||||||
|
authbasic = "auth:basic" dictionary
|
||||||
|
authbearer = "auth:bearer" dictionary
|
||||||
|
|
||||||
|
script = scriptreq | scriptres
|
||||||
|
scriptreq = "script:pre-request" st* "{" nl* textblock tagend
|
||||||
|
scriptres = "script:post-response" st* "{" nl* textblock tagend
|
||||||
|
tests = "tests" st* "{" nl* textblock tagend
|
||||||
|
docs = "docs" st* "{" nl* textblock tagend
|
||||||
|
}`);
|
||||||
|
|
||||||
|
const mapPairListToKeyValPairs = (pairList = [], parseEnabled = true) => {
|
||||||
|
if (!pairList.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return _.map(pairList[0], (pair) => {
|
||||||
|
let name = _.keys(pair)[0];
|
||||||
|
let value = pair[name];
|
||||||
|
|
||||||
|
if (!parseEnabled) {
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let enabled = true;
|
||||||
|
if (name && name.length && name.charAt(0) === '~') {
|
||||||
|
name = name.slice(1);
|
||||||
|
enabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
value,
|
||||||
|
enabled
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const concatArrays = (objValue, srcValue) => {
|
||||||
|
if (_.isArray(objValue) && _.isArray(srcValue)) {
|
||||||
|
return objValue.concat(srcValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapPairListToKeyValPair = (pairList = []) => {
|
||||||
|
if (!pairList || !pairList.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.merge({}, ...pairList[0]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const sem = grammar.createSemantics().addAttribute('ast', {
|
||||||
|
BruFile(tags) {
|
||||||
|
if (!tags || !tags.ast || !tags.ast.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return _.reduce(
|
||||||
|
tags.ast,
|
||||||
|
(result, item) => {
|
||||||
|
return _.mergeWith(result, item, concatArrays);
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dictionary(_1, _2, pairlist, _3) {
|
||||||
|
return pairlist.ast;
|
||||||
|
},
|
||||||
|
pairlist(_1, pair, _2, rest, _3) {
|
||||||
|
return [pair.ast, ...rest.ast];
|
||||||
|
},
|
||||||
|
pair(_1, key, _2, _3, _4, value, _5) {
|
||||||
|
let res = {};
|
||||||
|
res[key.ast] = value.ast ? value.ast.trim() : '';
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
key(chars) {
|
||||||
|
return chars.sourceString ? chars.sourceString.trim() : '';
|
||||||
|
},
|
||||||
|
value(chars) {
|
||||||
|
return chars.sourceString ? chars.sourceString.trim() : '';
|
||||||
|
},
|
||||||
|
textblock(line, _1, rest) {
|
||||||
|
return [line.ast, ...rest.ast].join('\n');
|
||||||
|
},
|
||||||
|
textline(chars) {
|
||||||
|
return chars.sourceString;
|
||||||
|
},
|
||||||
|
textchar(char) {
|
||||||
|
return char.sourceString;
|
||||||
|
},
|
||||||
|
nl(_1, _2) {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
st(_) {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
tagend(_1, _2) {
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
_iter(...elements) {
|
||||||
|
return elements.map((e) => e.ast);
|
||||||
|
},
|
||||||
|
meta(_1, dictionary) {
|
||||||
|
let meta = mapPairListToKeyValPair(dictionary.ast) || {};
|
||||||
|
|
||||||
|
meta.type = 'collection';
|
||||||
|
|
||||||
|
return {
|
||||||
|
meta
|
||||||
|
};
|
||||||
|
},
|
||||||
|
auth(_1, dictionary) {
|
||||||
|
let auth = mapPairListToKeyValPair(dictionary.ast) || {};
|
||||||
|
|
||||||
|
return {
|
||||||
|
auth: {
|
||||||
|
mode: auth ? auth.mode : 'none'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
query(_1, dictionary) {
|
||||||
|
return {
|
||||||
|
query: mapPairListToKeyValPairs(dictionary.ast)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
headers(_1, dictionary) {
|
||||||
|
return {
|
||||||
|
headers: mapPairListToKeyValPairs(dictionary.ast)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
authbasic(_1, dictionary) {
|
||||||
|
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||||
|
const usernameKey = _.find(auth, { name: 'username' });
|
||||||
|
const passwordKey = _.find(auth, { name: 'password' });
|
||||||
|
const username = usernameKey ? usernameKey.value : '';
|
||||||
|
const password = passwordKey ? passwordKey.value : '';
|
||||||
|
return {
|
||||||
|
auth: {
|
||||||
|
basic: {
|
||||||
|
username,
|
||||||
|
password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
authbearer(_1, dictionary) {
|
||||||
|
const auth = mapPairListToKeyValPairs(dictionary.ast, false);
|
||||||
|
const tokenKey = _.find(auth, { name: 'token' });
|
||||||
|
const token = tokenKey ? tokenKey.value : '';
|
||||||
|
return {
|
||||||
|
auth: {
|
||||||
|
bearer: {
|
||||||
|
token
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
varsreq(_1, dictionary) {
|
||||||
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
||||||
|
_.each(vars, (v) => {
|
||||||
|
let name = v.name;
|
||||||
|
if (name && name.length && name.charAt(0) === '@') {
|
||||||
|
v.name = name.slice(1);
|
||||||
|
v.local = true;
|
||||||
|
} else {
|
||||||
|
v.local = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
vars: {
|
||||||
|
req: vars
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
varsres(_1, dictionary) {
|
||||||
|
const vars = mapPairListToKeyValPairs(dictionary.ast);
|
||||||
|
_.each(vars, (v) => {
|
||||||
|
let name = v.name;
|
||||||
|
if (name && name.length && name.charAt(0) === '@') {
|
||||||
|
v.name = name.slice(1);
|
||||||
|
v.local = true;
|
||||||
|
} else {
|
||||||
|
v.local = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
vars: {
|
||||||
|
res: vars
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
scriptreq(_1, _2, _3, _4, textblock, _5) {
|
||||||
|
return {
|
||||||
|
script: {
|
||||||
|
req: outdentString(textblock.sourceString)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
scriptres(_1, _2, _3, _4, textblock, _5) {
|
||||||
|
return {
|
||||||
|
script: {
|
||||||
|
res: outdentString(textblock.sourceString)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
tests(_1, _2, _3, _4, textblock, _5) {
|
||||||
|
return {
|
||||||
|
tests: outdentString(textblock.sourceString)
|
||||||
|
};
|
||||||
|
},
|
||||||
|
docs(_1, _2, _3, _4, textblock, _5) {
|
||||||
|
return {
|
||||||
|
docs: outdentString(textblock.sourceString)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const parser = (input) => {
|
||||||
|
const match = grammar.match(input);
|
||||||
|
|
||||||
|
if (match.succeeded()) {
|
||||||
|
return sem(match).ast;
|
||||||
|
} else {
|
||||||
|
throw new Error(match.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = parser;
|
@ -1,80 +1,9 @@
|
|||||||
const ohm = require('ohm-js');
|
const dotenv = require('dotenv');
|
||||||
const _ = require('lodash');
|
|
||||||
|
|
||||||
const grammar = ohm.grammar(`Env {
|
|
||||||
EnvFile = (entry)*
|
|
||||||
entry = st* key st* "=" st* value st* nl*
|
|
||||||
key = keychar*
|
|
||||||
value = valuechar*
|
|
||||||
keychar = ~(nl | st | nl | "=") any
|
|
||||||
valuechar = ~nl any
|
|
||||||
nl = "\\r"? "\\n"
|
|
||||||
st = " " | "\\t"
|
|
||||||
}`);
|
|
||||||
|
|
||||||
const concatArrays = (objValue, srcValue) => {
|
|
||||||
if (_.isArray(objValue) && _.isArray(srcValue)) {
|
|
||||||
return objValue.concat(srcValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const sem = grammar.createSemantics().addAttribute('ast', {
|
|
||||||
EnvFile(entries) {
|
|
||||||
return _.reduce(
|
|
||||||
entries.ast,
|
|
||||||
(result, item) => {
|
|
||||||
return _.mergeWith(result, item, concatArrays);
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
},
|
|
||||||
entry(_1, key, _2, _3, _4, value, _5, _6) {
|
|
||||||
return { [key.ast.trim()]: value.ast.trim() };
|
|
||||||
},
|
|
||||||
key(chars) {
|
|
||||||
return chars.sourceString;
|
|
||||||
},
|
|
||||||
value(chars) {
|
|
||||||
return chars.sourceString;
|
|
||||||
},
|
|
||||||
nl(_1, _2) {
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
st(_) {
|
|
||||||
return '';
|
|
||||||
},
|
|
||||||
_iter(...elements) {
|
|
||||||
return elements.map((e) => e.ast);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const parser = (input) => {
|
const parser = (input) => {
|
||||||
const match = grammar.match(input);
|
const buf = Buffer.from(input);
|
||||||
|
const parsed = dotenv.parse(buf);
|
||||||
if (match.succeeded()) {
|
return parsed;
|
||||||
const ast = sem(match).ast;
|
|
||||||
return postProcessEntries(ast);
|
|
||||||
} else {
|
|
||||||
throw new Error(match.message);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function postProcessEntries(ast) {
|
|
||||||
const processed = {};
|
|
||||||
|
|
||||||
for (const key in ast) {
|
|
||||||
const value = ast[key];
|
|
||||||
|
|
||||||
if (!isNaN(value)) {
|
|
||||||
processed[key] = parseFloat(value); // Convert to number if it's a valid number
|
|
||||||
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
||||||
processed[key] = value.toLowerCase() === 'true'; // Convert to boolean if it's 'true' or 'false'
|
|
||||||
} else {
|
|
||||||
processed[key] = value; // Otherwise, keep it as a string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return processed;
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = parser;
|
module.exports = parser;
|
||||||
|
@ -138,6 +138,14 @@ ${indentString(body.text)}
|
|||||||
${indentString(body.xml)}
|
${indentString(body.xml)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body && body.sparql && body.sparql.length) {
|
||||||
|
bru += `body:sparql {
|
||||||
|
${indentString(body.sparql)}
|
||||||
|
}
|
||||||
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
185
packages/bruno-lang/v2/src/jsonToCollectionBru.js
Normal file
185
packages/bruno-lang/v2/src/jsonToCollectionBru.js
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const { indentString } = require('../../v1/src/utils');
|
||||||
|
|
||||||
|
const enabled = (items = []) => items.filter((item) => item.enabled);
|
||||||
|
const disabled = (items = []) => items.filter((item) => !item.enabled);
|
||||||
|
|
||||||
|
// remove the last line if two new lines are found
|
||||||
|
const stripLastLine = (text) => {
|
||||||
|
if (!text || !text.length) return text;
|
||||||
|
|
||||||
|
return text.replace(/(\r?\n)$/, '');
|
||||||
|
};
|
||||||
|
|
||||||
|
const jsonToCollectionBru = (json) => {
|
||||||
|
const { meta, query, headers, auth, script, tests, vars, docs } = json;
|
||||||
|
|
||||||
|
let bru = '';
|
||||||
|
|
||||||
|
if (meta) {
|
||||||
|
bru += 'meta {\n';
|
||||||
|
for (const key in meta) {
|
||||||
|
bru += ` ${key}: ${meta[key]}\n`;
|
||||||
|
}
|
||||||
|
bru += '}\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query && query.length) {
|
||||||
|
bru += 'query {';
|
||||||
|
if (enabled(query).length) {
|
||||||
|
bru += `\n${indentString(
|
||||||
|
enabled(query)
|
||||||
|
.map((item) => `${item.name}: ${item.value}`)
|
||||||
|
.join('\n')
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled(query).length) {
|
||||||
|
bru += `\n${indentString(
|
||||||
|
disabled(query)
|
||||||
|
.map((item) => `~${item.name}: ${item.value}`)
|
||||||
|
.join('\n')
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bru += '\n}\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (headers && headers.length) {
|
||||||
|
bru += 'headers {';
|
||||||
|
if (enabled(headers).length) {
|
||||||
|
bru += `\n${indentString(
|
||||||
|
enabled(headers)
|
||||||
|
.map((item) => `${item.name}: ${item.value}`)
|
||||||
|
.join('\n')
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disabled(headers).length) {
|
||||||
|
bru += `\n${indentString(
|
||||||
|
disabled(headers)
|
||||||
|
.map((item) => `~${item.name}: ${item.value}`)
|
||||||
|
.join('\n')
|
||||||
|
)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bru += '\n}\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth && auth.mode) {
|
||||||
|
bru += `auth {
|
||||||
|
${indentString(`mode: ${auth.mode}`)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth && auth.basic) {
|
||||||
|
bru += `auth:basic {
|
||||||
|
${indentString(`username: ${auth.basic.username}`)}
|
||||||
|
${indentString(`password: ${auth.basic.password}`)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auth && auth.bearer) {
|
||||||
|
bru += `auth:bearer {
|
||||||
|
${indentString(`token: ${auth.bearer.token}`)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let reqvars = _.get(vars, 'req');
|
||||||
|
let resvars = _.get(vars, 'res');
|
||||||
|
if (reqvars && reqvars.length) {
|
||||||
|
const varsEnabled = _.filter(reqvars, (v) => v.enabled && !v.local);
|
||||||
|
const varsDisabled = _.filter(reqvars, (v) => !v.enabled && !v.local);
|
||||||
|
const varsLocalEnabled = _.filter(reqvars, (v) => v.enabled && v.local);
|
||||||
|
const varsLocalDisabled = _.filter(reqvars, (v) => !v.enabled && v.local);
|
||||||
|
|
||||||
|
bru += `vars:pre-request {`;
|
||||||
|
|
||||||
|
if (varsEnabled.length) {
|
||||||
|
bru += `\n${indentString(varsEnabled.map((item) => `${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsLocalEnabled.length) {
|
||||||
|
bru += `\n${indentString(varsLocalEnabled.map((item) => `@${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsDisabled.length) {
|
||||||
|
bru += `\n${indentString(varsDisabled.map((item) => `~${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsLocalDisabled.length) {
|
||||||
|
bru += `\n${indentString(varsLocalDisabled.map((item) => `~@${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bru += '\n}\n\n';
|
||||||
|
}
|
||||||
|
if (resvars && resvars.length) {
|
||||||
|
const varsEnabled = _.filter(resvars, (v) => v.enabled && !v.local);
|
||||||
|
const varsDisabled = _.filter(resvars, (v) => !v.enabled && !v.local);
|
||||||
|
const varsLocalEnabled = _.filter(resvars, (v) => v.enabled && v.local);
|
||||||
|
const varsLocalDisabled = _.filter(resvars, (v) => !v.enabled && v.local);
|
||||||
|
|
||||||
|
bru += `vars:post-response {`;
|
||||||
|
|
||||||
|
if (varsEnabled.length) {
|
||||||
|
bru += `\n${indentString(varsEnabled.map((item) => `${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsLocalEnabled.length) {
|
||||||
|
bru += `\n${indentString(varsLocalEnabled.map((item) => `@${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsDisabled.length) {
|
||||||
|
bru += `\n${indentString(varsDisabled.map((item) => `~${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (varsLocalDisabled.length) {
|
||||||
|
bru += `\n${indentString(varsLocalDisabled.map((item) => `~@${item.name}: ${item.value}`).join('\n'))}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
bru += '\n}\n\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script && script.req && script.req.length) {
|
||||||
|
bru += `script:pre-request {
|
||||||
|
${indentString(script.req)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (script && script.res && script.res.length) {
|
||||||
|
bru += `script:post-response {
|
||||||
|
${indentString(script.res)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tests && tests.length) {
|
||||||
|
bru += `tests {
|
||||||
|
${indentString(tests)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (docs && docs.length) {
|
||||||
|
bru += `docs {
|
||||||
|
${indentString(docs)}
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stripLastLine(bru);
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = jsonToCollectionBru;
|
24
packages/bruno-lang/v2/tests/collection.spec.js
Normal file
24
packages/bruno-lang/v2/tests/collection.spec.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const collectionBruToJson = require('../src/collectionBruToJson');
|
||||||
|
const jsonToCollectionBru = require('../src/jsonToCollectionBru');
|
||||||
|
|
||||||
|
describe('collectionBruToJson', () => {
|
||||||
|
it('should parse the collection bru file', () => {
|
||||||
|
const input = fs.readFileSync(path.join(__dirname, 'fixtures', 'collection.bru'), 'utf8');
|
||||||
|
const expected = require('./fixtures/collection.json');
|
||||||
|
const output = collectionBruToJson(input);
|
||||||
|
|
||||||
|
expect(output).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('jsonToCollectionBru', () => {
|
||||||
|
it('should convert the collection json to bru', () => {
|
||||||
|
const input = require('./fixtures/collection.json');
|
||||||
|
const expected = fs.readFileSync(path.join(__dirname, 'fixtures', 'collection.bru'), 'utf8');
|
||||||
|
const output = jsonToCollectionBru(input);
|
||||||
|
|
||||||
|
expect(output).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user