diff --git a/package-lock.json b/package-lock.json index b799370ff..52f0082ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3429,6 +3429,18 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", + "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.43.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.27.2", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", @@ -3513,6 +3525,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@braintree/sanitize-url": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.0.2.tgz", + "integrity": "sha512-NVf/1YycDMs6+FxS0Tb/W8MjJRDQdXF+tBfDtZ5UZeiRUkTmwKc4vmYCKZTyymfJk1gnMsauvZSX/HiV9jOABw==", + "license": "MIT" + }, "node_modules/@bufbuild/protobuf": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.2.3.tgz", @@ -7312,6 +7330,13 @@ } } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -7976,6 +8001,617 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@swagger-api/apidom-ast": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ast/-/apidom-ast-1.0.1.tgz", + "integrity": "sha512-inTGo5b49XkHs/Vq48VafXnCzZrwwE+KTNcdfMybdm3RQTbfVFbvSUrS54WoHoaSbef1GsB9rnS/oXoXfNr72g==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-error": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "unraw": "^3.0.0" + } + }, + "node_modules/@swagger-api/apidom-core": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-core/-/apidom-core-1.0.1.tgz", + "integrity": "sha512-biA53spAUphP2IMJSPdqcRFjvLbvLspv1mJQrZpePUq4XGxGOKOHof7dUc1bXJuYvl6OOxOwnVniv5oK2Wyblw==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@types/ramda": "~0.30.0", + "minim": "~0.23.8", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "short-unique-id": "^5.3.2", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-error/-/apidom-error-1.0.1.tgz", + "integrity": "sha512-uwduVNLg9a2qA+Pl4b8gPERH6Xhvm/Ilv4iKMUOpUicLwNmYjrlcRsyYxLvFiNlTghm70xuI3hap1iaXbrer4A==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.20.7" + } + }, + "node_modules/@swagger-api/apidom-json-pointer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-json-pointer/-/apidom-json-pointer-1.0.1.tgz", + "integrity": "sha512-Dgxd9hl1AiCIM1b5f4dSfmP+rGtASUso8Lw51+az605hqrohgykxt8voiQtaJxKySWYbS1J9Vz2xjLwrEmfTKg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swaggerexpert/json-pointer": "^2.10.1" + } + }, + "node_modules/@swagger-api/apidom-ns-api-design-systems": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-api-design-systems/-/apidom-ns-api-design-systems-1.0.1.tgz", + "integrity": "sha512-frRfiLjcufeBgqnHQOcXgl6dnvcIdP4+18pWb+qT3N+dv87geJBk1CbXo6RjW9AEQX/7BtvWYkfSMLrh8q2TZA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-arazzo-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-arazzo-1/-/apidom-ns-arazzo-1-1.0.1.tgz", + "integrity": "sha512-gJJY0vmi3TJtlCYNPsZahQnP6GA9GtJX1a9jEUASaYYxz6cBBfY39y8C5tQTo0Jvc7QEff/UZ8iGX0kltcwlzA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-2/-/apidom-ns-asyncapi-2-1.0.1.tgz", + "integrity": "sha512-d8HBr2EVB2gK8FKrxj0Wsss6Qeael//WxnNv1ZtmNeCe1l83iC9RO/hjjq/OFob94ZTOfpPRNwGqZBd4tMxOnA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-asyncapi-3": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-asyncapi-3/-/apidom-ns-asyncapi-3-1.0.1.tgz", + "integrity": "sha512-vonGt1ScMlT+GbbSGa/+oe874Zl7NVylX3ZoMAhMkRyqu49vmWB6dXUcWw6ZsZu2GxVphjNTm+D52Ikw8UAMWg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2019-09": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2019-09/-/apidom-ns-json-schema-2019-09-1.0.1.tgz", + "integrity": "sha512-wtVkFhkM7a0ybjAR0HCQyXzAwIWWcaoHmXiZGAS7wTpI2sDMLwCrBXBuISa7BoRkG4ieA2odDF5Eac73knWUbA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-7": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-2020-12": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-2020-12/-/apidom-ns-json-schema-2020-12-1.0.1.tgz", + "integrity": "sha512-YBE5kYKARFWi+8HAiUVJxF9WpkdlW2ebH6K4oZt6mnOWROkPS+30Kjjxlz+Q994KhSZGBHKK0d3TU11xXUqyAQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-2019-09": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-4": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-4/-/apidom-ns-json-schema-draft-4-1.0.1.tgz", + "integrity": "sha512-OR3D7EXVq2H07n9uPKpNCikKC5857Pggbi1g5rt9X0znaUgxTtkYu8unPfbEcjQgFVglzIwqbGhMnahUFXYaTA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.1", + "@swagger-api/apidom-core": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-6": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-6/-/apidom-ns-json-schema-draft-6-1.0.1.tgz", + "integrity": "sha512-J9/aSU9/YwiRU/avkSN1APYPs9sYVyrzaMpfq7XIY8xMFC4buwrPzCymrqshSOLbn1Qzr9Ruavcqx5Bwt0EuRg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-json-schema-draft-7": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-json-schema-draft-7/-/apidom-ns-json-schema-draft-7-1.0.1.tgz", + "integrity": "sha512-b8pNff3epzweot5Edoa05mM/jBMgvjqajvNTOvOa8SNPWHWLjSJNYkBT2jI3BnFqqEMMo7litEfKnblblFuDtQ==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-6": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.4" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-2/-/apidom-ns-openapi-2-1.0.1.tgz", + "integrity": "sha512-XZ1xHHvsZZaNNHkqk0KWggZxMM2Av5eJdjbxwLij7TFWjodYVJAMZLyWG15llDBjnTXQYtpFIVLGjndf2oC7Xg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-0": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-0/-/apidom-ns-openapi-3-0-1.0.1.tgz", + "integrity": "sha512-BAypZcl8NO+jYpAmAXJVCLVe4f+v3ZoZN21Oxu03N2lsv+qz/P9vpl+6C0dOfta6X8fACkuGr4KIGU6leUPpJg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-draft-4": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-ns-openapi-3-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-ns-openapi-3-1/-/apidom-ns-openapi-3-1-1.0.1.tgz", + "integrity": "sha512-nIkgyIW8XTV+zjzLKxP1JaA/lpgmtsRBLshh1mL+Fspd+RYAhyMpRDRNvBOmkIhva9Dst9LNYyMjBP9ssfKUwg==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.1", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-json-pointer": "^1.0.1", + "@swagger-api/apidom-ns-json-schema-2020-12": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "ts-mixer": "^6.0.3" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-json/-/apidom-parser-adapter-api-design-systems-json-1.0.1.tgz", + "integrity": "sha512-Udj6vJ6Au+fcqZJZtgHlUi1Y/ImLHo8fx4ICSas7hewA7z1/eZ7Y3Yp8YSGZ7ZwqXHS6nnm5fBHws0DgAplnDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-api-design-systems-yaml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-api-design-systems-yaml/-/apidom-parser-adapter-api-design-systems-yaml-1.0.1.tgz", + "integrity": "sha512-YZ2IuTEzUGXRF8oFuHHGZpM15hRnSI/rZnweGT984bX53HXi1NFpZdNxOz49vmkhFz6XJgxRXp1R3EDN/98urg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-api-design-systems": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-json-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-json-1/-/apidom-parser-adapter-arazzo-json-1-1.0.1.tgz", + "integrity": "sha512-Re8EcgYOITyTXvGeJyE/4ZNsprkSrkXVmHzyM9hqTWoMpDILnqOtbrjc0YwLkbe9awBkUMWJ51CEFLYALSYfDA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-arazzo-yaml-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-arazzo-yaml-1/-/apidom-parser-adapter-arazzo-yaml-1-1.0.1.tgz", + "integrity": "sha512-++BB47Vf9sarAf+YCvlt9V2OqFd8O5AZMr/xXBKTls4SzwpUcLf4oIQJpHcr/rl+bgI4CKwxKXHvnhg2vhEVmg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-2/-/apidom-parser-adapter-asyncapi-json-2-1.0.1.tgz", + "integrity": "sha512-dd8djfZy2utM1xO7oxDPB/dmExSFgEA2l71gjHaKmhJw7O5NB8E/1663w9lD4NElj2Ft8kuGLLDsqyKNhXW/9w==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-json-3": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-json-3/-/apidom-parser-adapter-asyncapi-json-3-1.0.1.tgz", + "integrity": "sha512-qD+gbnSnc9PlZ8b45knyWihrWOMKhnAHDnvzRllX+NiyM3XdBJ7B6yLn8dl6gZuKjTBHoMORLEefoTGvmskneA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-3": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-2/-/apidom-parser-adapter-asyncapi-yaml-2-1.0.1.tgz", + "integrity": "sha512-G5RS0pCFCOIsFflKvWbH+DblunmcAdVi5X9ETTTkGLa1IF5s0DIdjU46WxJAzEQpCXOvmUhpDspjaAYXxGTYpQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-asyncapi-yaml-3/-/apidom-parser-adapter-asyncapi-yaml-3-1.0.1.tgz", + "integrity": "sha512-hzgUkTsuKYraY0NXQlaYe/j1/LkvNF/8r30Iz7/1B27BYLOKIwHoFGN6jUa8UBA9/0qSp8QPzSwVWFLrgNiqJQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-3": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-json": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-json/-/apidom-parser-adapter-json-1.0.1.tgz", + "integrity": "sha512-95V2aMBGZ76rYXcod/PCJpVEMK+9mPk/gDgsDKEq2ka+YVMTtR1tUkPUIBmWlNC+brh5reks1QocyQL8B4f3TQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.1", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.21.1", + "tree-sitter-json": "=0.24.8", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-2/-/apidom-parser-adapter-openapi-json-2-1.0.1.tgz", + "integrity": "sha512-Gp02eAA32SN+hAgVDc82xpkUthHn0oAdLdri5g3co4pa45XVRsBSHS3L/H3NEbhDJ8wpXjcg+FLs/OAwcm42yw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-0": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-0/-/apidom-parser-adapter-openapi-json-3-0-1.0.1.tgz", + "integrity": "sha512-Ls3U0stAtMqvzesy981crjDa7vwqGHlCoulHIsWQ/V74dHg3Nl1Vg9AgerefKg8LHxxLiYTZJITfsWQMYsnlkA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-json-3-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-json-3-1/-/apidom-parser-adapter-openapi-json-3-1-1.0.1.tgz", + "integrity": "sha512-Xsf3jUCfgqZqZjiABWifPynBDLPkW54V210Oa4SvgyI7ZWgubcy5/Wgd68wvhq4knauRXqRmbgKc+WRm0UP3xw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-2/-/apidom-parser-adapter-openapi-yaml-2-1.0.1.tgz", + "integrity": "sha512-SygtOXG9XF6lYveg6rymk4u1Twgk1VTxzyVkQ8I6eQoGyBoYfJC00sI6qep9bGU/VnsaRaN/H8+N0vXBeAOsPg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-0/-/apidom-parser-adapter-openapi-yaml-3-0-1.0.1.tgz", + "integrity": "sha512-xm8tY1NYe329tGF01WCtCi7uepppORWs3WpwzskSiZnDAmyjIu5ez3R0RFPCiXnMRGgj4wO6UzjawSrKGTJHjA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-openapi-yaml-3-1/-/apidom-parser-adapter-openapi-yaml-3-1-1.0.1.tgz", + "integrity": "sha512-p0G3g63Jcd4Z5Y2hStNB0NgjwYJg9VBLhkDcmFdmKCbz9vYA45rMN+wn62pqkWQE7KBZ1F1zY0wacRlUy0VuuQ==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-parser-adapter-yaml-1-2/-/apidom-parser-adapter-yaml-1-2-1.0.1.tgz", + "integrity": "sha512-ppNo8mncbGA3TchroLmcDv1WUw9vruHa4M96WbWqI7cwH3zdJ1UddwfHkZ5IaCOUU08Iyo2uzMMRaarALAsl8g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-ast": "^1.0.1", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@tree-sitter-grammars/tree-sitter-yaml": "=0.7.1", + "@types/ramda": "~0.30.0", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0", + "tree-sitter": "=0.22.4", + "web-tree-sitter": "=0.24.5" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/@swagger-api/apidom-parser-adapter-yaml-1-2/node_modules/tree-sitter": { + "version": "0.22.4", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.22.4.tgz", + "integrity": "sha512-usbHZP9/oxNsUY65MQUsduGRqDHQOou1cagUSwjhoSYAmSahjQDAVsh9s+SlZkn8X8+O1FULRGwHu7AFP3kjzg==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + } + }, + "node_modules/@swagger-api/apidom-reference": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@swagger-api/apidom-reference/-/apidom-reference-1.0.1.tgz", + "integrity": "sha512-FvM6cooFx1ppWN9gKXSLFG2Y4u3SRdv1FIJxj+5VC/6V3++BF2LUFkb7hK0IOaAjw2vQ7G0NUyP+5UY/3qKBjA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.26.10", + "@swagger-api/apidom-core": "^1.0.1", + "@swagger-api/apidom-error": "^1.0.1", + "@types/ramda": "~0.30.0", + "axios": "^1.12.2", + "minimatch": "^7.4.3", + "process": "^0.11.10", + "ramda": "~0.30.0", + "ramda-adjunct": "^5.0.0" + }, + "optionalDependencies": { + "@swagger-api/apidom-json-pointer": "^1.0.1", + "@swagger-api/apidom-ns-arazzo-1": "^1.0.1", + "@swagger-api/apidom-ns-asyncapi-2": "^1.0.1", + "@swagger-api/apidom-ns-openapi-2": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-0": "^1.0.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-json": "^1.0.1", + "@swagger-api/apidom-parser-adapter-api-design-systems-yaml": "^1.0.1", + "@swagger-api/apidom-parser-adapter-arazzo-json-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-arazzo-yaml-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-asyncapi-json-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-asyncapi-json-3": "^1.0.1", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-asyncapi-yaml-3": "^1.0.1", + "@swagger-api/apidom-parser-adapter-json": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-json-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-json-3-0": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-json-3-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-2": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-0": "^1.0.1", + "@swagger-api/apidom-parser-adapter-openapi-yaml-3-1": "^1.0.1", + "@swagger-api/apidom-parser-adapter-yaml-1-2": "^1.0.1" + } + }, + "node_modules/@swagger-api/apidom-reference/node_modules/minimatch": { + "version": "7.4.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-7.4.6.tgz", + "integrity": "sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@swaggerexpert/cookie": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/cookie/-/cookie-2.0.2.tgz", + "integrity": "sha512-DPI8YJ0Vznk4CT+ekn3rcFNq1uQwvUHZhH6WvTSPD0YKBIlMS9ur2RYKghXuxxOiqOam/i4lHJH4xTIiTgs3Mg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.3" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/@swaggerexpert/json-pointer": { + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/@swaggerexpert/json-pointer/-/json-pointer-2.10.2.tgz", + "integrity": "sha512-qMx1nOrzoB+PF+pzb26Q4Tc2sOlrx9Ba2UBNX9hB31Omrq+QoZ2Gly0KLrQWw4Of1AQ4J9lnD+XOdwOdcdXqqw==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/@swc/counter": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", @@ -8184,6 +8820,36 @@ "node": ">= 10" } }, + "node_modules/@tree-sitter-grammars/tree-sitter-yaml": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@tree-sitter-grammars/tree-sitter-yaml/-/tree-sitter-yaml-0.7.1.tgz", + "integrity": "sha512-AynBwkIoQCTgjDR33bDUp9Mqq+YTco0is3n5hRApMqG9of/6A4eQsfC1/uSEeHSUyMQSYawcAWamsexnVpIP4Q==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4" + }, + "peerDependencies": { + "tree-sitter": "^0.22.4" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/@tree-sitter-grammars/tree-sitter-yaml/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -8326,6 +8992,15 @@ "@types/node": "*" } }, + "node_modules/@types/hast": { + "version": "2.3.10", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", + "integrity": "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2" + } + }, "node_modules/@types/hoist-non-react-statics": { "version": "3.3.6", "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz", @@ -8531,6 +9206,15 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==" }, + "node_modules/@types/ramda": { + "version": "0.30.2", + "resolved": "https://registry.npmjs.org/@types/ramda/-/ramda-0.30.2.tgz", + "integrity": "sha512-PyzHvjCalm2BRYjAU6nIB3TprYwMNOUY/7P/N8bSzp9W/yM2YrtGtAnnVtaCNSeOZ8DzKyFDvaqQs7LnWwwmBA==", + "license": "MIT", + "dependencies": { + "types-ramda": "^0.30.1" + } + }, "node_modules/@types/react": { "version": "18.3.18", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.18.tgz", @@ -8600,6 +9284,18 @@ "license": "MIT", "optional": true }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.10", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", @@ -9485,6 +10181,12 @@ "node": ">= 8" } }, + "node_modules/apg-lite": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/apg-lite/-/apg-lite-1.0.5.tgz", + "integrity": "sha512-SlI+nLMQDzCZfS39ihzjGp3JNBQfJXyMi6cg9tkLOCPVErgFsUIAEdO9IezR7kbP5Xd0ozcPNQBkf9TO5cHgWw==", + "license": "BSD-2-Clause" + }, "node_modules/append-field": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", @@ -9686,6 +10388,15 @@ "node": ">=10.12.0" } }, + "node_modules/autolinker": { + "version": "3.16.2", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-3.16.2.tgz", + "integrity": "sha512-JiYl7j2Z19F9NdTmirENSUUIIL/9MytEWtmzhfmsKPCp9E+G35Y0UNCMoM9tFigxT59qSc8Ml2dlZXOCVTYwuA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.3.0" + } + }, "node_modules/autoprefixer": { "version": "10.4.20", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", @@ -9775,13 +10486,13 @@ } }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", + "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, @@ -9797,17 +10508,6 @@ "js-md4": "^0.3.2" } }, - "node_modules/axios-ntlm/node_modules/axios": { - "version": "1.7.9", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", - "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/babel-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", @@ -10200,7 +10900,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -10786,6 +11485,36 @@ "node": ">=10" } }, + "node_modules/character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/check-error": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", @@ -11382,6 +12111,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz", + "integrity": "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", @@ -11700,6 +12439,17 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/core-js-pure": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", + "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", + "hasInstallScript": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -12115,7 +12865,6 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, "license": "MIT" }, "node_modules/cssesc": { @@ -12467,6 +13216,15 @@ "node": ">=6" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -12891,6 +13649,15 @@ "url": "https://dotenvx.com" } }, + "node_modules/drange": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz", + "integrity": "sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -14313,6 +15080,12 @@ "integrity": "sha512-HEomBtr2fYaVX3iaRdcVLU7Qd3SQhCYvXlMMM9RNaihfIaj5bIC7ADqw/bAPSg/uyX6FIBPq69ioXq0B4Cb6eA==", "license": "MIT" }, + "node_modules/fast-json-patch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-json-patch/-/fast-json-patch-3.1.1.tgz", + "integrity": "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -14373,6 +15146,19 @@ "reusify": "^1.0.4" } }, + "node_modules/fault": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/fault/-/fault-1.0.4.tgz", + "integrity": "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -14814,6 +15600,14 @@ "node": ">= 6" } }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, "node_modules/formik": { "version": "2.4.6", "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.6.tgz", @@ -15543,6 +16337,33 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-parse-selector": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", + "integrity": "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz", + "integrity": "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^2.0.0", + "comma-separated-tokens": "^1.0.0", + "hast-util-parse-selector": "^2.0.0", + "property-information": "^5.0.0", + "space-separated-tokens": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -15571,6 +16392,21 @@ "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==", "license": "MIT" }, + "node_modules/highlight.js": { + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", + "integrity": "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/highlightjs-vue": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz", + "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", + "license": "CC0-1.0" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -16117,7 +16953,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, "funding": [ { "type": "github", @@ -16285,6 +17120,15 @@ "node": ">= 0.10" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/ip-address": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", @@ -16318,6 +17162,30 @@ "node": ">= 0.10" } }, + "node_modules/is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", @@ -16395,6 +17263,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -16451,6 +17329,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-invalid-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-invalid-path/-/is-invalid-path-0.1.0.tgz", @@ -17862,6 +18750,12 @@ "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", "license": "BSD-3-Clause" }, + "node_modules/js-file-download": { + "version": "0.4.12", + "resolved": "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz", + "integrity": "sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg==", + "license": "MIT" + }, "node_modules/js-md4": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/js-md4/-/js-md4-0.3.2.tgz", @@ -18579,7 +19473,6 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, "license": "MIT" }, "node_modules/lodash.flow": { @@ -18698,6 +19591,20 @@ "node": ">=8" } }, + "node_modules/lowlight": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", + "integrity": "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==", + "license": "MIT", + "dependencies": { + "fault": "^1.0.0", + "highlight.js": "~10.7.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -19238,6 +20145,18 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/minim": { + "version": "0.23.8", + "resolved": "https://registry.npmjs.org/minim/-/minim-0.23.8.tgz", + "integrity": "sha512-bjdr2xW1dBCMsMGGsUeqM4eFI60m94+szhxWys+B1ztIt6gWSfeGBdSVCIawezeHYLYn0j6zrsXdQS/JllBzww==", + "license": "MIT", + "dependencies": { + "lodash": "^4.15.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -19490,6 +20409,15 @@ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "license": "MIT" }, + "node_modules/neotraverse": { + "version": "0.6.18", + "resolved": "https://registry.npmjs.org/neotraverse/-/neotraverse-0.6.18.tgz", + "integrity": "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, "node_modules/new-github-issue-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/new-github-issue-url/-/new-github-issue-url-0.2.1.tgz", @@ -19510,6 +20438,12 @@ "tslib": "^2.0.3" } }, + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "license": "MIT" + }, "node_modules/node-addon-api": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", @@ -19517,6 +20451,26 @@ "license": "MIT", "optional": true }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -19537,6 +20491,23 @@ } } }, + "node_modules/node-fetch-commonjs": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.3.2.tgz", + "integrity": "sha512-VBlAiynj3VMLrotgwOS3OyECFxas5y7ltLcK4t41lMUZeaK15Ym4QRkqN0EQKAFL42q9i21EPKjzLUPfltR72A==", + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -19817,6 +20788,30 @@ "node": ">=6" } }, + "node_modules/openapi-path-templating": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/openapi-path-templating/-/openapi-path-templating-2.2.1.tgz", + "integrity": "sha512-eN14VrDvl/YyGxxrkGOHkVkWEoPyhyeydOUrbvjoz8K5eIGgELASwN1eqFOJ2CTQMGCy2EntOK1KdtJ8ZMekcg==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/openapi-server-url-templating": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/openapi-server-url-templating/-/openapi-server-url-templating-1.3.0.tgz", + "integrity": "sha512-DPlCms3KKEbjVQb0spV6Awfn6UWNheuG/+folQPzh/wUaKwuqvj8zt5gagD7qoyxtE03cIiKPgLFS3Q8Bz00uQ==", + "license": "Apache-2.0", + "dependencies": { + "apg-lite": "^1.0.4" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -20001,6 +20996,24 @@ "node": ">= 0.10" } }, + "node_modules/parse-entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz", + "integrity": "sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==", + "license": "MIT", + "dependencies": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -21493,6 +22506,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/prismjs": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -21582,6 +22604,19 @@ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==", "license": "MIT" }, + "node_modules/property-information": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz", + "integrity": "sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/protobufjs": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", @@ -21817,11 +22852,49 @@ "@jitl/quickjs-ffi-types": "0.29.2" } }, + "node_modules/ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.30.1.tgz", + "integrity": "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda" + } + }, + "node_modules/ramda-adjunct": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ramda-adjunct/-/ramda-adjunct-5.1.0.tgz", + "integrity": "sha512-8qCpl2vZBXEJyNbi4zqcgdfHtcdsWjOGbiNSEnEBrM6Y0OKOT8UxJbIVGm1TIcjaSu2MxaWcgtsNlKlCk7o7qg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/ramda-adjunct" + }, + "peerDependencies": { + "ramda": ">= 0.30.0" + } + }, + "node_modules/randexp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.5.3.tgz", + "integrity": "sha512-U+5l2KrcMNOUPYvazA3h5ekF80FHTUG+87SEAmHZmolh1M+i/WyTCxVzmi+tidIa1tM4BSe8g2Y/D3loWDjj+w==", + "license": "MIT", + "dependencies": { + "drange": "^1.0.2", + "ret": "^0.2.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -21931,6 +23004,19 @@ "react": "^15.3.0 || 16 || 17 || 18" } }, + "node_modules/react-debounce-input": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/react-debounce-input/-/react-debounce-input-3.3.0.tgz", + "integrity": "sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==", + "license": "MIT", + "dependencies": { + "lodash.debounce": "^4", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": "^15.3.0 || 16 || 17 || 18" + } + }, "node_modules/react-dnd": { "version": "16.0.1", "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", @@ -22026,6 +23112,29 @@ } } }, + "node_modules/react-immutable-proptypes": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/react-immutable-proptypes/-/react-immutable-proptypes-2.2.0.tgz", + "integrity": "sha512-Vf4gBsePlwdGvSZoLSBfd4HAP93HDauMY4fDjXhreg/vg6F3Fj/MXDNyTbltPC/xZKmZc+cjLu3598DdYK6sgQ==", + "license": "MIT", + "dependencies": { + "invariant": "^2.2.2" + }, + "peerDependencies": { + "immutable": ">=3.6.2" + } + }, + "node_modules/react-immutable-pure-component": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-immutable-pure-component/-/react-immutable-pure-component-2.2.2.tgz", + "integrity": "sha512-vkgoMJUDqHZfXXnjVlG3keCxSO/U6WeDQ5/Sl0GK2cH8TOxEzQ5jXqDXHEL/jqk6fsNxV05oH5kD7VNMUE2k+A==", + "license": "MIT", + "peerDependencies": { + "immutable": ">= 2 || >= 4.0.0-rc", + "react": ">= 16.6", + "react-dom": ">= 16.6" + } + }, "node_modules/react-inspector": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.2.tgz", @@ -22233,6 +23342,23 @@ } } }, + "node_modules/react-syntax-highlighter": { + "version": "15.6.6", + "resolved": "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.6.tgz", + "integrity": "sha512-DgXrc+AZF47+HvAPEmn7Ua/1p10jNoVZVI/LoPiYdtY+OM+/nG5yefLHKJwdKqY1adMuHFbeyBaG9j64ML7vTw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.3.1", + "highlight.js": "^10.4.1", + "highlightjs-vue": "^1.0.0", + "lowlight": "^1.17.0", + "prismjs": "^1.30.0", + "refractor": "^3.6.0" + }, + "peerDependencies": { + "react": ">= 0.14.0" + } + }, "node_modules/react-textarea-autosize": { "version": "8.5.9", "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", @@ -22513,6 +23639,30 @@ "redux": "^4" } }, + "node_modules/refractor": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz", + "integrity": "sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==", + "license": "MIT", + "dependencies": { + "hastscript": "^6.0.0", + "parse-entities": "^2.0.0", + "prismjs": "~1.27.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/refractor/node_modules/prismjs": { + "version": "1.27.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz", + "integrity": "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -22610,6 +23760,37 @@ "node": ">= 0.10" } }, + "node_modules/remarkable": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-2.0.1.tgz", + "integrity": "sha512-YJyMcOH5lrR+kZdmB0aJJ4+93bEojRZ1HGDn9Eagu6ibg7aVZhc3OWbbShRid+Q5eAfsEqWxpe+g5W5nYNfNiA==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "^3.11.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/remarkable/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/remarkable/node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -22713,6 +23894,15 @@ "entities": "^2.0.0" } }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -22826,6 +24016,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ret": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", + "integrity": "sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -23961,7 +25160,6 @@ "version": "2.4.11", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.1", @@ -24025,6 +25223,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/short-unique-id": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/short-unique-id/-/short-unique-id-5.3.2.tgz", + "integrity": "sha512-KRT/hufMSxXKEDSQujfVE0Faa/kZ51ihUcZQAcmP04t00DvPj7Ox5anHke1sJYUtzSuiT/Y5uyzg/W7bBEGhCg==", + "license": "Apache-2.0", + "bin": { + "short-unique-id": "bin/short-unique-id", + "suid": "bin/short-unique-id" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -24310,6 +25518,16 @@ "source-map": "^0.6.0" } }, + "node_modules/space-separated-tokens": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz", + "integrity": "sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/spawn-command": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2.tgz", @@ -25010,6 +26228,175 @@ "node": ">= 10" } }, + "node_modules/swagger-client": { + "version": "3.36.0", + "resolved": "https://registry.npmjs.org/swagger-client/-/swagger-client-3.36.0.tgz", + "integrity": "sha512-9fkjxGHXuKy20jj8zwE6RwgFSOGKAyOD5U7aKgW/+/futtHZHOdZeqiEkb97sptk2rdBv7FEiUQDNlWZR186RA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.22.15", + "@scarf/scarf": "=1.4.0", + "@swagger-api/apidom-core": "^1.0.0-rc.1", + "@swagger-api/apidom-error": "^1.0.0-rc.1", + "@swagger-api/apidom-json-pointer": "^1.0.0-rc.1", + "@swagger-api/apidom-ns-openapi-3-1": "^1.0.0-rc.1", + "@swagger-api/apidom-reference": "^1.0.0-rc.1", + "@swaggerexpert/cookie": "^2.0.2", + "deepmerge": "~4.3.0", + "fast-json-patch": "^3.0.0-1", + "js-yaml": "^4.1.0", + "neotraverse": "=0.6.18", + "node-abort-controller": "^3.1.1", + "node-fetch-commonjs": "^3.3.2", + "openapi-path-templating": "^2.2.1", + "openapi-server-url-templating": "^1.3.0", + "ramda": "^0.30.1", + "ramda-adjunct": "^5.1.0" + } + }, + "node_modules/swagger-ui-react": { + "version": "5.17.12", + "resolved": "https://registry.npmjs.org/swagger-ui-react/-/swagger-ui-react-5.17.12.tgz", + "integrity": "sha512-qkDBOx9c3v1m8LyUgyc+Idz8UXLmz7RMDYX0Xlry0kwBQYxkw6AXfQ1bemgkna1sRQCvASmucdm2TYAdx6nlaA==", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime-corejs3": "^7.24.5", + "@braintree/sanitize-url": "=7.0.2", + "base64-js": "^1.5.1", + "classnames": "^2.5.1", + "css.escape": "1.5.1", + "deep-extend": "0.6.0", + "dompurify": "=3.1.4", + "ieee754": "^1.2.1", + "immutable": "^3.x.x", + "js-file-download": "^0.4.12", + "js-yaml": "=4.1.0", + "lodash": "^4.17.21", + "prop-types": "^15.8.1", + "randexp": "^0.5.3", + "randombytes": "^2.1.0", + "react-copy-to-clipboard": "5.1.0", + "react-debounce-input": "=3.3.0", + "react-immutable-proptypes": "2.2.0", + "react-immutable-pure-component": "^2.2.0", + "react-inspector": "^6.0.1", + "react-redux": "^9.1.2", + "react-syntax-highlighter": "^15.5.0", + "redux": "^5.0.1", + "redux-immutable": "^4.0.0", + "remarkable": "^2.0.1", + "reselect": "^5.1.0", + "serialize-error": "^8.1.0", + "sha.js": "^2.4.11", + "swagger-client": "^3.28.1", + "url-parse": "^1.5.10", + "xml": "=1.0.1", + "xml-but-prettier": "^1.0.1", + "zenscroll": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.8.0 <19", + "react-dom": ">=16.8.0 <19" + } + }, + "node_modules/swagger-ui-react/node_modules/dompurify": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.4.tgz", + "integrity": "sha512-2gnshi6OshmuKil8rMZuQCGiUF3cUxHY3NGDzUAdUx/NPEe5DVnO8BDoAQouvgwnx0R/+a6jUn36Z0FSdq8vww==", + "license": "(MPL-2.0 OR Apache-2.0)" + }, + "node_modules/swagger-ui-react/node_modules/immutable": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-3.8.2.tgz", + "integrity": "sha512-15gZoQ38eYjEjxkorfbcgBKBL6R7T459OuK+CpcWt7O3KF4uPCx2tD0uFETlUDIyo+1789crbMhTvQBSR5yBMg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/swagger-ui-react/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/swagger-ui-react/node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/swagger-ui-react/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/swagger-ui-react/node_modules/redux-immutable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/redux-immutable/-/redux-immutable-4.0.0.tgz", + "integrity": "sha512-SchSn/DWfGb3oAejd+1hhHx01xUoxY+V7TeK0BKqpkLKiQPVFf7DYzEaKmrEVxsWxielKfSK9/Xq66YyxgR1cg==", + "license": "BSD-3-Clause", + "dependencies": { + "immutable": "3.8.2" + } + }, + "node_modules/swagger-ui-react/node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/swagger-ui-react/node_modules/serialize-error": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", + "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/swagger-ui-react/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -25666,6 +27053,58 @@ "tree-kill": "cli.js" } }, + "node_modules/tree-sitter": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz", + "integrity": "sha512-7dxoA6kYvtgWw80265MyqJlkRl4yawIjO7S5MigytjELkX43fV2WsAXzsNfO7sBpPPCF5Gp0+XzHk0DwLCq3xQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.8.0" + } + }, + "node_modules/tree-sitter-json": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/tree-sitter-json/-/tree-sitter-json-0.24.8.tgz", + "integrity": "sha512-Tc9ZZYwHyWZ3Tt1VEw7Pa2scu1YO7/d2BCBbKTx5hXwig3UfdQjsOPkPyLpDJOn/m1UBEWYAtSdGAwCSyagBqQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^8.2.2", + "node-gyp-build": "^4.8.2" + }, + "peerDependencies": { + "tree-sitter": "^0.21.1" + }, + "peerDependenciesMeta": { + "tree-sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-json/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "node_modules/tree-sitter/node_modules/node-addon-api": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.5.0.tgz", + "integrity": "sha512-/bRZty2mXUIFY/xU5HLvveNHlswNJej+RnxBjOMkidWfwZzgTbPG1E3K5TOxRLOR+5hX7bSofy8yf1hZevMS8A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/truncate-utf8-bytes": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/truncate-utf8-bytes/-/truncate-utf8-bytes-1.0.2.tgz", @@ -25758,6 +27197,18 @@ "node": ">=10" } }, + "node_modules/ts-mixer": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", + "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", + "license": "MIT" + }, + "node_modules/ts-toolbelt": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz", + "integrity": "sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==", + "license": "Apache-2.0" + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -25848,6 +27299,15 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, + "node_modules/types-ramda": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/types-ramda/-/types-ramda-0.30.1.tgz", + "integrity": "sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==", + "license": "MIT", + "dependencies": { + "ts-toolbelt": "^9.6.0" + } + }, "node_modules/typescript": { "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", @@ -25987,6 +27447,12 @@ "node": ">= 0.8" } }, + "node_modules/unraw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unraw/-/unraw-3.0.0.tgz", + "integrity": "sha512-08/DA66UF65OlpUDIQtbJyrqTR0jTAlJ+jsnkQ4jxR7+K5g5YG1APZKQSMCE1vqqmD+2pv6+IdEjmopFatacvg==", + "license": "MIT" + }, "node_modules/unzip-crx-3": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/unzip-crx-3/-/unzip-crx-3-0.2.0.tgz", @@ -26166,6 +27632,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -26336,6 +27811,22 @@ "node": ">=10.13.0" } }, + "node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/web-tree-sitter": { + "version": "0.24.5", + "resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.24.5.tgz", + "integrity": "sha512-+J/2VSHN8J47gQUAvF8KDadrfz6uFYVjxoxbKWDoXVsH2u7yLdarCnIURnrMA6uSRkgX3SdmqM5BOoQjPdSh5w==", + "license": "MIT", + "optional": true + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -26667,6 +28158,21 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/xml-but-prettier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-but-prettier/-/xml-but-prettier-1.0.1.tgz", + "integrity": "sha512-C2CJaadHrZTqESlH03WOyw0oZTtoy2uEg6dSDF6YRg+9GnYNub53RRemLpnvtbHDFelxMx4LajiFsYeR6XJHgQ==", + "license": "MIT", + "dependencies": { + "repeat-string": "^1.5.2" + } + }, "node_modules/xml-formatter": { "version": "3.6.3", "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.6.3.tgz", @@ -26846,6 +28352,12 @@ "node": ">=10" } }, + "node_modules/zenscroll": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zenscroll/-/zenscroll-4.0.2.tgz", + "integrity": "sha512-jEA1znR7b4C/NnaycInCU6h/d15ZzCd1jmsruqOKnZP6WXQSMH3W2GL+OXbkruslU4h+Tzuos0HdswzRUk/Vgg==", + "license": "Unlicense" + }, "node_modules/zod": { "version": "3.24.4", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", @@ -26903,6 +28415,7 @@ "i18next": "24.1.2", "idb": "^7.0.0", "immer": "^9.0.15", + "js-yaml": "^4.1.0", "jsesc": "^3.0.2", "jshint": "^2.13.6", "json5": "^2.2.3", @@ -26942,9 +28455,11 @@ "shell-quote": "^1.8.3", "strip-json-comments": "^5.0.1", "styled-components": "^5.3.3", + "swagger-ui-react": "5.17.12", "system": "^2.0.1", "url": "^0.11.3", "xml-formatter": "^3.5.0", + "xml2js": "^0.6.2", "yup": "^0.32.11" }, "devDependencies": { @@ -29596,17 +31111,6 @@ "node": ">=18.0.0" } }, - "packages/bruno-cli/node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "packages/bruno-cli/node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -31562,17 +33066,6 @@ "electron-builder-squirrel-windows": "24.13.3" } }, - "packages/bruno-electron/node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "packages/bruno-electron/node_modules/builder-util": { "version": "24.13.1", "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-24.13.1.tgz", @@ -32241,17 +33734,6 @@ "npm": ">=9.0.0" } }, - "packages/bruno-requests/node_modules/axios": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "packages/bruno-requests/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -32360,17 +33842,6 @@ "ws": "^8.18.3" } }, - "packages/bruno-tests/node_modules/axios": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.3.tgz", - "integrity": "sha512-iP4DebzoNlP/YN2dpwCgb8zoCmhtkajzS48JvwmkSkXvPI3DHc7m+XYL5tGnSlJtR6nImXZmdCuN5aP8dh1d8A==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "packages/bruno-tests/node_modules/cookie": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", diff --git a/packages/bruno-app/package.json b/packages/bruno-app/package.json index 9def0c5dd..ca6441f91 100644 --- a/packages/bruno-app/package.json +++ b/packages/bruno-app/package.json @@ -45,6 +45,8 @@ "idb": "^7.0.0", "immer": "^9.0.15", "jsesc": "^3.0.2", + "js-yaml": "^4.1.0", + "xml2js": "^0.6.2", "jshint": "^2.13.6", "json5": "^2.2.3", "jsonc-parser": "^3.2.1", @@ -83,6 +85,7 @@ "shell-quote": "^1.8.3", "strip-json-comments": "^5.0.1", "styled-components": "^5.3.3", + "swagger-ui-react": "5.17.12", "system": "^2.0.1", "url": "^0.11.3", "xml-formatter": "^3.5.0", diff --git a/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js new file mode 100644 index 000000000..a0b69853c --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/Plugins/Yaml/index.js @@ -0,0 +1,129 @@ +const yamlPlugin = (cm) => { + cm.defineMode('yaml', function () { + var cons = ['true', 'false', 'on', 'off', 'yes', 'no']; + var keywordRegex = new RegExp('\\b((' + cons.join(')|(') + '))$', 'i'); + + return { + token: function (stream, state) { + var ch = stream.peek(); + var esc = state.escaped; + state.escaped = false; + /* comments */ + if (ch == '#' && (stream.pos == 0 || /\s/.test(stream.string.charAt(stream.pos - 1)))) { + stream.skipToEnd(); + return 'comment'; + } + + if (stream.match(/^('([^']|\\.)*'?|"([^"]|\\.)*"?)/)) return 'string'; + + if (state.literal && stream.indentation() > state.keyCol) { + stream.skipToEnd(); + return 'string'; + } else if (state.literal) { + state.literal = false; + } + if (stream.sol()) { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + /* document start */ + if (stream.match('---')) { + return 'def'; + } + /* document end */ + if (stream.match('...')) { + return 'def'; + } + /* array list item */ + if (stream.match(/\s*-\s+/)) { + return 'meta'; + } + } + /* inline pairs/lists */ + if (stream.match(/^(\{|\}|\[|\])/)) { + if (ch == '{') state.inlinePairs++; + else if (ch == '}') state.inlinePairs--; + else if (ch == '[') state.inlineList++; + else state.inlineList--; + return 'meta'; + } + + /* list separator */ + if (state.inlineList > 0 && !esc && ch == ',') { + stream.next(); + return 'meta'; + } + /* pairs separator */ + if (state.inlinePairs > 0 && !esc && ch == ',') { + state.keyCol = 0; + state.pair = false; + state.pairStart = false; + stream.next(); + return 'meta'; + } + + /* start of value of a pair */ + if (state.pairStart) { + /* block literals */ + if (stream.match(/^\s*(\||\>)\s*/)) { + state.literal = true; + return 'meta'; + } + /* references */ + if (stream.match(/^\s*(\&|\*)[a-z0-9\._-]+\b/i)) { + return 'variable-2'; + } + /* numbers */ + if (state.inlinePairs == 0 && stream.match(/^\s*-?[0-9\.\,]+\s?$/)) { + return 'number'; + } + if (state.inlinePairs > 0 && stream.match(/^\s*-?[0-9\.\,]+\s?(?=(,|}))/)) { + return 'number'; + } + /* keywords */ + if (stream.match(keywordRegex)) { + return 'keyword'; + } + } + + /* pairs (associative arrays) -> key */ + if ( + !state.pair + && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/) + ) { + state.pair = true; + state.keyCol = stream.indentation(); + return 'atom'; + } + if (state.pair && stream.match(/^:\s*/)) { + state.pairStart = true; + return 'meta'; + } + + /* nothing found, continue */ + state.pairStart = false; + state.escaped = ch == '\\'; + stream.next(); + return null; + }, + startState: function () { + return { + pair: false, + pairStart: false, + keyCol: 0, + inlinePairs: 0, + inlineList: 0, + literal: false, + escaped: false + }; + }, + lineComment: '#', + fold: 'indent' + }; + }); + + cm.defineMIME('text/x-yaml', 'yaml'); + cm.defineMIME('text/yaml', 'yaml'); +}; + +export default yamlPlugin; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js new file mode 100644 index 000000000..320a8f6c1 --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/StyledWrapper.js @@ -0,0 +1,65 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + div.CodeMirror { + height: calc(100vh - 4rem); + background: ${(props) => props.theme.codemirror.bg}; + border: solid 1px ${(props) => props.theme.codemirror.border}; + font-family: ${(props) => (props.font ? props.font : 'default')}; + line-break: anywhere; + } + + .CodeMirror-dialog { + overflow: visible; + input { + background: transparent; + border: 1px solid #d3d6db; + outline: none; + border-radius: 0px; + } + } + + .CodeMirror-overlayscroll-horizontal div, + .CodeMirror-overlayscroll-vertical div { + background: #d2d7db; + } + + textarea.cm-editor { + position: relative; + } + + // Todo: dark mode temporary fix + // Clean this + .CodeMirror.cm-s-monokai { + .CodeMirror-overlayscroll-horizontal div, + .CodeMirror-overlayscroll-vertical div { + background: #444444; + } + } + + .cm-s-monokai span.cm-property, + .cm-s-monokai span.cm-attribute { + color: #9cdcfe !important; + } + + .cm-s-monokai span.cm-string { + color: #ce9178 !important; + } + + .cm-s-monokai span.cm-number { + color: #b5cea8 !important; + } + + .cm-s-monokai span.cm-atom { + color: #569cd6 !important; + } + + .cm-variable-valid { + color: green; + } + .cm-variable-invalid { + color: red; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js new file mode 100644 index 000000000..7f88c99e9 --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/CodeEditor/index.js @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2021 GraphQL Contributors. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import React from 'react'; +import StyledWrapper from './StyledWrapper'; +import yamlPlugin from './Plugins/Yaml/index'; + +let CodeMirror; +const SERVER_RENDERED = typeof window === 'undefined' || global['PREVENT_CODEMIRROR_RENDER'] === true; + +if (!SERVER_RENDERED) { + CodeMirror = require('codemirror'); +} + +export default class CodeEditor extends React.Component { + constructor(props) { + super(props); + this.cachedValue = props.value || ''; + this.variables = {}; + this.lintOptions = { + esversion: 11, + expr: true, + asi: true + }; + } + + componentWillMount() { + switch (this.props.mode) { + case 'yaml': + // YAML linting and hightlighting plugin + yamlPlugin(CodeMirror); + break; + default: + break; + } + } + + componentDidMount() { + const editor = (this.editor = CodeMirror(this._node, { + value: this.props.value || '', + lineNumbers: true, + lineWrapping: true, + tabSize: 2, + mode: this.props.mode || 'application/text', + keyMap: 'sublime', + autoCloseBrackets: true, + matchBrackets: true, + showCursorWhenSelecting: true, + foldGutter: true, + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter', 'CodeMirror-lint-markers'], + lint: this.lintOptions, + readOnly: this.props.readOnly, + scrollbarStyle: 'overlay', + theme: this.props.theme === 'dark' ? 'monokai' : 'default', + extraKeys: { + 'Cmd-S': () => { + if (this.props.onSave) { + this.props.onSave(); + } + }, + 'Ctrl-S': () => { + if (this.props.onSave) { + this.props.onSave(); + } + }, + 'Cmd-F': 'findPersistent', + 'Ctrl-F': 'findPersistent', + 'Cmd-H': 'replace', + 'Ctrl-H': 'replace', + 'Tab': function (cm) { + cm.getSelection().includes('\n') || editor.getLine(cm.getCursor().line) == cm.getSelection() + ? cm.execCommand('indentMore') + : cm.replaceSelection(' ', 'end'); + }, + 'Shift-Tab': 'indentLess', + 'Ctrl-Space': 'autocomplete', + 'Cmd-Space': 'autocomplete', + 'Ctrl-Y': 'foldAll', + 'Cmd-Y': 'foldAll', + 'Ctrl-I': 'unfoldAll', + 'Cmd-I': 'unfoldAll' + } + })); + if (editor) { + editor.setOption('lint', this.props.mode && editor.getValue().trim().length > 0 ? this.lintOptions : false); + editor.on('change', this._onEdit); + } + } + + componentDidUpdate(prevProps) { + this.ignoreChangeEvent = true; + if (this.props.value !== prevProps.value && this.props.value !== this.cachedValue && this.editor) { + this.cachedValue = this.props.value; + this.editor.setValue(this.props.value); + } + if (this.props.theme !== prevProps.theme && this.editor) { + this.editor.setOption('theme', this.props.theme === 'dark' ? 'monokai' : 'default'); + } + this.ignoreChangeEvent = false; + } + + componentWillUnmount() { + if (this.editor) { + this.editor.off('change', this._onEdit); + this.editor = null; + } + } + + render() { + if (this.editor) { + this.editor.refresh(); + } + return ( + { + this._node = node; + }} + /> + ); + } + + _onEdit = () => { + if (!this.ignoreChangeEvent && this.editor) { + this.editor.setOption('lint', this.editor.getValue().trim().length > 0 ? this.lintOptions : false); + this.cachedValue = this.editor.getValue(); + if (this.props.onEdit) { + this.props.onEdit(this.cachedValue); + } + } + }; +} diff --git a/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js new file mode 100644 index 000000000..49c569bd7 --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/FileEditor/index.js @@ -0,0 +1,51 @@ +import get from 'lodash/get'; +import { useTheme } from 'providers/Theme'; +import { useDispatch, useSelector } from 'react-redux'; +import CodeEditor from './CodeEditor/index'; +import { IconDeviceFloppy } from '@tabler/icons'; +import { saveApiSpecToFile } from 'providers/ReduxStore/slices/apiSpec'; +import { useState } from 'react'; + +const FileEditor = ({ apiSpec }) => { + const dispatch = useDispatch(); + const { displayedTheme, theme } = useTheme(); + const preferences = useSelector((state) => state.app.preferences); + + const [content, setContent] = useState(apiSpec?.raw); + + const onEdit = (value) => { + setContent(value); + }; + + const onSave = () => { + dispatch(saveApiSpecToFile({ uid: apiSpec?.uid, content })); + }; + + const hasChanges = Boolean(content != apiSpec?.raw); + + const editorMode = 'yaml'; + + return ( +
+ + +
+ ); +}; + +export default FileEditor; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js b/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js new file mode 100644 index 000000000..21dd2882d --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/StyledWrapper.js @@ -0,0 +1,19 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .swagger-root { + height: calc(100vh - 4rem); + border: solid 1px ${(props) => props.theme.codemirror.border}; + + &.dark { + .swagger-ui { + filter: invert(88%) hue-rotate(180deg); + } + .swagger-ui .microlight { + filter: invert(100%) hue-rotate(180deg); + } + } + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js b/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js new file mode 100644 index 000000000..3e59f3bc5 --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/Renderers/Swagger/index.js @@ -0,0 +1,19 @@ +import SwaggerUI from 'swagger-ui-react'; +import StyledWrapper from './StyledWrapper'; +import { useTheme } from 'providers/Theme'; + +const Swagger = ({ string }) => { + const { displayedTheme } = useTheme(); + + console.log('string', string); + + return ( + +
+ +
+
+ ); +}; + +export default Swagger; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js b/packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js new file mode 100644 index 000000000..f50e7b5fc --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/StyledWrapper.js @@ -0,0 +1,22 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .menu-icon { + cursor: pointer; + color: ${(props) => props.theme.sidebar.dropdownIcon.color}; + } + + div.dropdown-item.menu-item { + color: ${(props) => props.theme.colors.danger}; + &:hover { + background-color: ${(props) => props.theme.colors.bg.danger}; + color: white; + } + } + + .react-tooltip { + z-index: 10; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ApiSpecPanel/index.js b/packages/bruno-app/src/components/ApiSpecPanel/index.js new file mode 100644 index 000000000..f6359ffdb --- /dev/null +++ b/packages/bruno-app/src/components/ApiSpecPanel/index.js @@ -0,0 +1,97 @@ +import React, { forwardRef, useRef } from 'react'; +import find from 'lodash/find'; +import { useSelector, useDispatch } from 'react-redux'; +import { IconFileCode, IconDots } from '@tabler/icons'; +import StyledWrapper from './StyledWrapper'; +import FileEditor from './FileEditor'; +import Dropdown from 'components/Dropdown'; +import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; +import { useState } from 'react'; +import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec'; +import { Suspense } from 'react'; +import Swagger from './Renderers/Swagger'; +import toast from 'react-hot-toast'; + +const ApiSpecPanel = () => { + const dispatch = useDispatch(); + + const [createApiSpecModalOpen, setCreateApiSpecModalOpen] = useState(false); + + const { apiSpecs, activeApiSpecUid } = useSelector((state) => state.apiSpec); + + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + + let apiSpec = find(apiSpecs, (c) => c.uid === activeApiSpecUid); + const { filename, pathname, raw, uid } = apiSpec || {}; + if (!uid) { + return
API Spec not found!
; + } + + const MenuIcon = forwardRef((props, ref) => { + return ( +
+ +
+ ); + }); + + const handleOpenApiSpec = () => { + dispatch(openApiSpec()).catch( + (err) => console.log(err) && toast.error('An error occurred while opening the API spec') + ); + }; + + return ( + + {createApiSpecModalOpen ? setCreateApiSpecModalOpen(false)} /> : null} +
+
+
+ + API Designer +
+
+
+ {filename} +
+
+ } placement="bottom-start"> +
{ + dropdownTippyRef.current.hide(); + setCreateApiSpecModalOpen(true); + }} + > + Create API Spec +
+
{ + dropdownTippyRef.current.hide(); + handleOpenApiSpec(); + }} + > + Open API Spec +
+
+
+
+
+
+
+ +
+
+ + + +
+
+
+
+ ); +}; + +export default ApiSpecPanel; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js new file mode 100644 index 000000000..54b81c4ec --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/StyledWrapper.js @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .api-specs-badge { + margin-inline: 0.5rem; + background-color: ${(props) => props.theme.sidebar.badge.bg}; + border-radius: 5px; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js new file mode 100644 index 000000000..65022d1cf --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecBadge/index.js @@ -0,0 +1,21 @@ +import { IconFileCode } from '@tabler/icons'; +import StyledWrapper from './StyledWrapper'; + +const ApiSpecsBadge = () => { + return ( + +
+
+
+ + + + APIs +
+
+
+
+ ); +}; + +export default ApiSpecsBadge; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js new file mode 100644 index 000000000..d175d55ae --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/ApiSpecItem/index.js @@ -0,0 +1,64 @@ +import { setActiveApiSpecUid } from 'providers/ReduxStore/slices/apiSpec'; +import { showApiSpecPage as _showApiSpecPage } from 'providers/ReduxStore/slices/app'; +import Dropdown from 'components/Dropdown'; +import { IconDots } from '@tabler/icons'; +import { useState, useRef } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import CloseApiSpec from '../CloseApiSpec/index'; +import { forwardRef } from 'react'; + +const ApiSpecItem = ({ apiSpec }) => { + const dispatch = useDispatch(); + + const activeApiSpecUid = useSelector((state) => state.apiSpec.activeApiSpecUid); + const showApiSpecPage = useSelector((state) => state.app.showApiSpecPage); + + const [closeApiSpecModal, setCloseApiSpecModal] = useState(false); + + const dropdownTippyRef = useRef(); + const onDropdownCreate = (ref) => (dropdownTippyRef.current = ref); + + const handleOpenApiSpec = (apiSpec) => (e) => { + dispatch(_showApiSpecPage()); + dispatch(setActiveApiSpecUid({ uid: apiSpec.uid })); + }; + + const MenuIcon = forwardRef((props, ref) => { + return ( +
+ +
+ ); + }); + + return ( +
+ {closeApiSpecModal && setCloseApiSpecModal(false)} />} +
+ {apiSpec?.name} +
+
+ } placement="bottom-start"> +
{ + dropdownTippyRef.current.hide(); + setCloseApiSpecModal(true); + }} + > + Close +
+
+
+
+ ); +}; + +export default ApiSpecItem; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js new file mode 100644 index 000000000..b7a525e9c --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CloseApiSpec/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import toast from 'react-hot-toast'; +import Modal from 'components/Modal'; +import { useDispatch } from 'react-redux'; +import { IconFileCode } from '@tabler/icons'; +import { closeApiSpecFile } from 'providers/ReduxStore/slices/apiSpec'; + +const CloseApiSpec = ({ onClose, apiSpec }) => { + const dispatch = useDispatch(); + + const onConfirm = () => { + dispatch(closeApiSpecFile({ uid: apiSpec.uid })) + .then(() => { + toast.success('API Spec closed'); + onClose(); + }) + .catch(() => toast.error('An error occurred while closing the API Spec')); + }; + + return ( + +
+ + {apiSpec.name} +
+
{apiSpec.pathname}
+
+ Are you sure you want to close API Spec {apiSpec.name} in Bruno? +
+
+ It will still be available in the file system at the above location and can be re-opened later. +
+
+ ); +}; + +export default CloseApiSpec; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js new file mode 100644 index 000000000..a154d2dff --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/StyledWrapper.js @@ -0,0 +1,15 @@ +import styled from 'styled-components'; + +const StyledWrapper = styled.div` + .api-spec-file-extension { + color: ${(props) => props.theme.colors.text.darkOrange}; + } + select { + background: ${(props) => props.theme.bg}; + } + option { + background: ${(props) => props.theme.bg}; + } +`; + +export default StyledWrapper; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js new file mode 100644 index 000000000..aab976b3e --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/CreateApiSpec/index.js @@ -0,0 +1,326 @@ +import React, { useRef, useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useFormik } from 'formik'; +import * as Yup from 'yup'; +import { browseDirectory } from 'providers/ReduxStore/slices/collections/actions'; +import toast from 'react-hot-toast'; +import Modal from 'components/Modal'; +import { createApiSpecFile } from 'providers/ReduxStore/slices/apiSpec'; +import { useState } from 'react'; +import StyledWrapper from './StyledWrapper'; +import { exportApiSpec } from 'utils/exporters/openapi-spec'; +import { each } from 'lodash'; +import { showApiSpecPage } from 'providers/ReduxStore/slices/app'; +import { validateName, validateNameError } from 'utils/common/regex'; + +export const getEnvironmentVariablesKeyValuePairs = (envVariables) => { + let variables = {}; + each(envVariables, (variable) => { + if (variable.name && variable.value && variable.enabled) { + variables[variable.name] = variable.value; + } + }); + return variables; +}; + +const CreateApiSpec = ({ onClose }) => { + const inputRef = useRef(); + const dispatch = useDispatch(); + const workspaces = useSelector((state) => state.workspaces.workspaces); + const activeWorkspaceUid = useSelector((state) => state.workspaces.activeWorkspaceUid); + const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid); + const [defaultApiSpecLocation, setDefaultApiSpecLocation] = React.useState(''); + + const isDefaultWorkspace = !activeWorkspace || activeWorkspace.type === 'default'; + + React.useEffect(() => { + const getDefaultLocation = async () => { + if (activeWorkspace && activeWorkspace.pathname && activeWorkspace.type !== 'default') { + try { + const { ipcRenderer } = window; + const apiSpecPath = await ipcRenderer.invoke('renderer:ensure-apispec-folder', activeWorkspace.pathname); + setDefaultApiSpecLocation(apiSpecPath); + } catch (error) { + console.error('Error getting apispec folder:', error); + } + } + }; + getDefaultLocation(); + }, [activeWorkspace]); + + const formik = useFormik({ + enableReinitialize: true, + initialValues: { + importFrom: 'blank', + collectionLocation: '', + environment: '', + apiSpecName: '', + apiSpecLocation: defaultApiSpecLocation || '' + }, + validationSchema: Yup.object({ + importFrom: Yup.string().oneOf(['blank', 'collection']), + collectionLocation: Yup.string().min(1, 'location is required'), + environment: Yup.string(), + apiSpecName: Yup.string() + .min(1, 'Must be at least 1 character') + .max(255, 'Must be 255 characters or less') + .test('is-valid-filename', function (value) { + const isValid = validateName(value); + return isValid ? true : this.createError({ message: validateNameError(value) }); + }) + .required('Name is required'), + apiSpecLocation: Yup.string().min(1, 'location is required').required('location is required') + }), + onSubmit: async (values) => { + let yamlContent = ''; + if (values?.importFrom === 'collection' && values?.collectionLocation && collectionData) { + const { files, envVariables, processEnvVariables } = collectionData; + let variables = { + processEnvVariables + }; + // Get selected env's variables + if (values?.environment && values?.environment?.length) { + variables = { + ...getEnvironmentVariablesKeyValuePairs(envVariables[values?.environment] || {}), + ...variables + }; + } + // Create API spec yaml + let exportedYamlContentData = exportApiSpec({ name: values?.apiSpecName, variables, items: files }); + if (exportedYamlContentData?.content) { + yamlContent = exportedYamlContentData?.content; + } + } + + dispatch(createApiSpecFile(`${values.apiSpecName}.yaml`, values.apiSpecLocation, yamlContent)) + .then(() => { + setTimeout(() => { + dispatch(showApiSpecPage()); + }, 200); + toast.success('ApiSpec created'); + onClose(); + }) + .catch((err) => toast.error(err?.message)); + } + }); + + const browse = () => { + dispatch(browseDirectory()) + .then((dirPath) => { + // When the user closes the diolog without selecting anything dirPath will be false + if (typeof dirPath === 'string') { + formik.setFieldValue('apiSpecLocation', dirPath); + } + }) + .catch((error) => { + formik.setFieldValue('apiSpecLocation', ''); + console.error(error); + }); + }; + + const browseCollection = () => { + dispatch(browseDirectory()) + .then((dirPath) => { + if (typeof dirPath === 'string') { + formik.setFieldValue('collectionLocation', dirPath); + } + }) + .catch((error) => { + formik.setFieldValue('collectionLocation', ''); + console.error(error); + }); + }; + + useEffect(() => { + if (inputRef && inputRef.current) { + inputRef.current.focus(); + } + }, [inputRef]); + + const [environments, setEnvironments] = useState([]); + const [collectionData, setCollectionData] = useState(null); + + useEffect(() => { + const collectionLocation = formik.values.collectionLocation; + if (collectionLocation) { + const { ipcRenderer } = window; + ipcRenderer + .invoke('renderer:get-collection-json', collectionLocation) + .then(({ files, name, envVariables, processEnvVariables }) => { + setCollectionData({ name, files, envVariables, processEnvVariables }); + const environments = envVariables || {}; + const environmentNames = Object.keys(environments); + if (environmentNames?.length) { + setEnvironments(environments); + formik.setFieldValue('environment', environmentNames[0] || ''); + } + }) + .catch((err) => { + console.error('Error loading collection:', err); + toast.error('Failed to load collection'); + }); + } + }, [formik.values.collectionLocation]); + + const onSubmit = () => formik.handleSubmit(); + + return ( + + +
e.preventDefault()}> +
+ +
+ + + + +
+ {formik.touched.importFrom && formik.errors.importFrom ? ( +
{formik.errors.importFrom}
+ ) : null} + {formik.values.importFrom === 'collection' ? ( + <> + + + {formik.touched.collectionLocation && formik.errors.collectionLocation ? ( +
{formik.errors.collectionLocation}
+ ) : null} +
+ + Browse + +
+ {environments && Object.keys(environments || {})?.length > 0 ? ( + <> + +
+ +
+ + ) : ( + <> + )} + + ) : ( + <> + )} + {formik.touched.environment && formik.errors.environment ? ( +
{formik.errors.environment}
+ ) : null} + +
+ { + formik.handleChange(e); + }} + autoComplete="off" + autoCorrect="off" + autoCapitalize="off" + spellCheck="false" + value={formik.values.apiSpecName || ''} + /> +
+ .yaml +
+
+ {formik.touched.apiSpecName && formik.errors.apiSpecName ? ( +
{formik.errors.apiSpecName}
+ ) : null} + + + + {formik.touched.apiSpecLocation && formik.errors.apiSpecLocation ? ( +
{formik.errors.apiSpecLocation}
+ ) : null} +
+ + Browse + + {!isDefaultWorkspace && ( + + (defaults to workspace's apispec folder) + + )} +
+
+
+
+
+ ); +}; + +export default CreateApiSpec; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js new file mode 100644 index 000000000..992087dc5 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/StyledWrapper.js @@ -0,0 +1,52 @@ +import styled from 'styled-components'; + +const Wrapper = styled.div` + .api-spec-item { + &.active { + background: ${(props) => props.theme.sidebar.collection.item.bg}; + } + &:hover { + background: ${(props) => props.theme.sidebar.collection.item.hoverBg}; + .menu-icon { + .dropdown { + div[aria-expanded='false'] { + visibility: visible; + } + } + } + } + } + + .menu-icon { + cursor: pointer; + color: ${(props) => props.theme.sidebar.dropdownIcon.color}; + + .dropdown { + div[aria-expanded='true'] { + visibility: visible; + } + div[aria-expanded='false'] { + visibility: hidden; + } + } + } + + div.tippy-box { + position: relative; + top: -0.625rem; + } + + div.dropdown-item.close-item { + color: ${(props) => props.theme.colors.danger}; + &:hover { + background-color: ${(props) => props.theme.colors.bg.danger}; + color: white; + } + } + + .placeholder { + color: ${(props) => props.theme.colors.text.muted}; + } +`; + +export default Wrapper; diff --git a/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js b/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js new file mode 100644 index 000000000..37801bd32 --- /dev/null +++ b/packages/bruno-app/src/components/Sidebar/ApiSpecs/index.js @@ -0,0 +1,77 @@ +import React from 'react'; +import styled from 'styled-components'; +import { useDispatch, useSelector } from 'react-redux'; +import { useTheme } from 'providers/Theme'; +import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; +import ApiSpecItem from './ApiSpecItem'; +import ApiSpecsBadge from './ApiSpecBadge'; +import StyledWrapper from './StyledWrapper'; +import toast from 'react-hot-toast'; + +const LinkStyle = styled.span` + color: ${(props) => props.theme['text-link']}; +`; + +const ApiSpecs = () => { + const dispatch = useDispatch(); + const { theme } = useTheme(); + const allApiSpecs = useSelector((state) => state.apiSpec.apiSpecs); + const workspaces = useSelector((state) => state.workspaces.workspaces); + const activeWorkspaceUid = useSelector((state) => state.workspaces.activeWorkspaceUid); + const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid); + + const apiSpecs = React.useMemo(() => { + if (!activeWorkspace) return []; + + const workspaceApiSpecs = activeWorkspace.apiSpecs || []; + + // Map workspace API specs to loaded API specs from Redux store + return workspaceApiSpecs.map((ws) => { + const loadedApiSpec = allApiSpecs.find((apiSpec) => apiSpec.pathname === ws.path); + return loadedApiSpec; + }).filter(Boolean); + }, [allApiSpecs, activeWorkspace, activeWorkspace?.apiSpecs]); + + const handleOpenApiSpec = () => { + dispatch(openApiSpec()).catch( + (err) => console.log(err) && toast.error('An error occurred while opening the API spec') + ); + }; + + const OpenLink = () => ( + handleOpenApiSpec()}> + Open + + ); + + if (!apiSpecs || !apiSpecs.length) { + return ( + + +
+
No API Specs found.
+
+ API Spec. +
+
+
+ ); + } + + return ( + +
+ +
+ {apiSpecs && apiSpecs.length + ? apiSpecs.map((apiSpec) => { + return ; + }) + : null} +
+
+
+ ); +}; + +export default ApiSpecs; diff --git a/packages/bruno-app/src/components/Sidebar/Collections/index.js b/packages/bruno-app/src/components/Sidebar/Collections/index.js index b438cb786..f38abccf1 100644 --- a/packages/bruno-app/src/components/Sidebar/Collections/index.js +++ b/packages/bruno-app/src/components/Sidebar/Collections/index.js @@ -7,6 +7,7 @@ import CreateOrOpenCollection from './CreateOrOpenCollection'; import CollectionSearch from './CollectionSearch/index'; import { useMemo } from 'react'; import { normalizePath } from 'utils/common/path'; +import ApiSpecs from '../ApiSpecs/index'; const Collections = ({ showSearch }) => { const [searchText, setSearchText] = useState(''); @@ -51,6 +52,8 @@ const Collections = ({ showSearch }) => { ); }) : null} +
+
); diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js index 457c9b826..4c20e241b 100644 --- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js +++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/StyledWrapper.js @@ -8,10 +8,6 @@ const StyledWrapper = styled.div` align-items: center; justify-content: space-between; gap: 8px; - } - - /* Section Title (single view mode) - with separator */ - &.single-view { border-bottom: 1px solid ${(props) => props.theme.sidebar.collection.item.hoverBg}; } diff --git a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js index 747501828..397a3f056 100644 --- a/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js +++ b/packages/bruno-app/src/components/Sidebar/SidebarHeader/index.js @@ -4,6 +4,7 @@ import { IconDeviceDesktop, IconDotsVertical, IconDownload, + IconFileCode, IconFolder, IconPlus, IconSearch, @@ -19,22 +20,19 @@ import { useDispatch, useSelector } from 'react-redux'; import { importCollection, openCollection } from 'providers/ReduxStore/slices/collections/actions'; import { sortCollections } from 'providers/ReduxStore/slices/collections/index'; import { importCollectionInWorkspace } from 'providers/ReduxStore/slices/workspaces/actions'; +import { openApiSpec } from 'providers/ReduxStore/slices/apiSpec'; import Dropdown from 'components/Dropdown'; import ImportCollection from 'components/Sidebar/ImportCollection'; import ImportCollectionLocation from 'components/Sidebar/ImportCollectionLocation'; +import CreateApiSpec from 'components/Sidebar/ApiSpecs/CreateApiSpec'; import RemoveCollectionsModal from '../Collections/RemoveCollectionsModal/index'; import CreateCollection from '../CreateCollection'; import StyledWrapper from './StyledWrapper'; -const VIEW_TABS = [ - { id: 'collections', label: 'Collections', icon: IconBox } -]; - -const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange }) => { +const SidebarHeader = ({ setShowSearch }) => { const dispatch = useDispatch(); - const { ipcRenderer } = window; const { workspaces, activeWorkspaceUid } = useSelector((state) => state.workspaces); const activeWorkspace = workspaces.find((w) => w.uid === activeWorkspaceUid); @@ -48,6 +46,7 @@ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange const [createCollectionModalOpen, setCreateCollectionModalOpen] = useState(false); const [importCollectionModalOpen, setImportCollectionModalOpen] = useState(false); const [importCollectionLocationModalOpen, setImportCollectionLocationModalOpen] = useState(false); + const [createApiSpecModalOpen, setCreateApiSpecModalOpen] = useState(false); const handleImportCollection = ({ rawData, type }) => { setImportCollectionModalOpen(false); @@ -148,6 +147,13 @@ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange }); }; + const handleOpenApiSpec = () => { + dispatch(openApiSpec()).catch((err) => { + console.error(err); + toast.error('An error occurred while opening the API spec'); + }); + }; + const renderModals = () => ( <> {createCollectionModalOpen && ( @@ -169,11 +175,14 @@ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange handleSubmit={handleImportCollectionLocation} /> )} + {createApiSpecModalOpen && ( + setCreateApiSpecModalOpen(false)} + /> + )} ); - const isSingleView = VIEW_TABS.length === 1; - // Render Collections-specific actions const renderCollectionsActions = () => ( <> @@ -227,6 +236,27 @@ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange Open collection +
API Specs
+
{ + setCreateApiSpecModalOpen(true); + addDropdownTippyRef.current?.hide(); + }} + > + + Create API Spec +
+
{ + handleOpenApiSpec(); + addDropdownTippyRef.current?.hide(); + }} + > + + Open API Spec +
{/* Actions dropdown (sort, close all, etc.) */} @@ -277,57 +307,18 @@ const SidebarHeader = ({ setShowSearch, activeView = 'collections', onViewChange ); - // Render Second Tab-specific actions - const renderSecondTabActions = () => ( - <> - {/* Add second tab actions here */} - - ); - - // Render the view switcher - either tabs or single title - const renderViewSwitcher = () => { - if (isSingleView) { - // Single view - just show the title - const tab = VIEW_TABS[0]; - const TabIcon = tab.icon; - return ( -
- - {tab.label} -
- ); - } - - // Multiple views - show segmented tabs - return ( -
- {VIEW_TABS.map((tab) => { - const TabIcon = tab.icon; - return ( - - ); - })} -
- ); - }; - return ( - + {renderModals()}
- {renderViewSwitcher()} +
+ + Collections +
{/* Action Buttons - Context Sensitive */}
- {activeView === 'collections' ? renderCollectionsActions() : renderSecondTabActions()} + {renderCollectionsActions()}
diff --git a/packages/bruno-app/src/components/Sidebar/index.js b/packages/bruno-app/src/components/Sidebar/index.js index 60dc98a24..2071909ea 100644 --- a/packages/bruno-app/src/components/Sidebar/index.js +++ b/packages/bruno-app/src/components/Sidebar/index.js @@ -15,7 +15,6 @@ const Sidebar = () => { const [asideWidth, setAsideWidth] = useState(leftSidebarWidth); const lastWidthRef = useRef(leftSidebarWidth); const [showSearch, setShowSearch] = useState(false); - const [activeView, setActiveView] = useState('collections'); // 'collections' or any other future tab const dispatch = useDispatch(); const [dragging, setDragging] = useState(false); @@ -85,18 +84,8 @@ const Sidebar = () => {
- {activeView === 'collections' ? ( - - ) : ( -
-

- Second tab content will appear here -

-
- )} +
diff --git a/packages/bruno-app/src/pages/Bruno/index.js b/packages/bruno-app/src/pages/Bruno/index.js index cb3dc7cc3..9ea176702 100644 --- a/packages/bruno-app/src/pages/Bruno/index.js +++ b/packages/bruno-app/src/pages/Bruno/index.js @@ -6,6 +6,7 @@ import RequestTabPanel from 'components/RequestTabPanel'; import Sidebar from 'components/Sidebar'; import StatusBar from 'components/StatusBar'; import AppTitleBar from 'components/AppTitleBar'; +import ApiSpecPanel from 'components/ApiSpecPanel'; // import ErrorCapture from 'components/ErrorCapture'; import { useSelector } from 'react-redux'; import { isElectron } from 'utils/common/platform'; @@ -13,6 +14,7 @@ import StyledWrapper from './StyledWrapper'; import 'codemirror/theme/material.css'; import 'codemirror/theme/monokai.css'; import 'codemirror/addon/scroll/simplescrollbars.css'; +import 'swagger-ui-react/swagger-ui.css'; import Devtools from 'components/Devtools'; import useGrpcEventListeners from 'utils/network/grpc-event-listeners'; import useWsEventListeners from 'utils/network/ws-event-listeners'; @@ -52,8 +54,10 @@ require('utils/codemirror/autocomplete'); export default function Main() { const activeTabUid = useSelector((state) => state.tabs.activeTabUid); + const activeApiSpecUid = useSelector((state) => state.apiSpec.activeApiSpecUid); const isDragging = useSelector((state) => state.app.isDragging); const showHomePage = useSelector((state) => state.app.showHomePage); + const showApiSpecPage = useSelector((state) => state.app.showApiSpecPage); const isConsoleOpen = useSelector((state) => state.logs.isConsoleOpen); const mainSectionRef = useRef(null); const [showRosettaBanner, setShowRosettaBanner] = useState(false); @@ -113,7 +117,9 @@ export default function Main() {
- {showHomePage ? ( + {showApiSpecPage && activeApiSpecUid ? ( + + ) : showHomePage ? ( ) : ( <> diff --git a/packages/bruno-app/src/providers/App/useIpcEvents.js b/packages/bruno-app/src/providers/App/useIpcEvents.js index a0ca0523d..3b79b7b36 100644 --- a/packages/bruno-app/src/providers/App/useIpcEvents.js +++ b/packages/bruno-app/src/providers/App/useIpcEvents.js @@ -30,6 +30,7 @@ import { globalEnvironmentsUpdateEvent, updateGlobalEnvironments } from 'provide import { collectionAddOauth2CredentialsByUrl, updateCollectionLoadingState } from 'providers/ReduxStore/slices/collections/index'; import { addLog } from 'providers/ReduxStore/slices/logs'; import { updateSystemResources } from 'providers/ReduxStore/slices/performance'; +import { apiSpecAddFileEvent, apiSpecChangeFileEvent } from 'providers/ReduxStore/slices/apiSpec'; const useIpcEvents = () => { const dispatch = useDispatch(); @@ -91,10 +92,25 @@ const useIpcEvents = () => { } }; + const _apiSpecTreeUpdated = (type, val) => { + if (window.__IS_DEV__) { + console.log('API Spec update:', type); + console.log(val); + } + if (type === 'addFile') { + dispatch(apiSpecAddFileEvent({ data: val })); + } + if (type === 'changeFile') { + dispatch(apiSpecChangeFileEvent({ data: val })); + } + }; + ipcRenderer.invoke('renderer:ready'); const removeCollectionTreeUpdateListener = ipcRenderer.on('main:collection-tree-updated', _collectionTreeUpdated); + const removeApiSpecTreeUpdateListener = ipcRenderer.on('main:apispec-tree-updated', _apiSpecTreeUpdated); + const removeOpenCollectionListener = ipcRenderer.on('main:collection-opened', (pathname, uid, brunoConfig) => { dispatch(openCollectionEvent(uid, pathname, brunoConfig)); }); @@ -267,6 +283,7 @@ const useIpcEvents = () => { return () => { removeCollectionTreeUpdateListener(); + removeApiSpecTreeUpdateListener(); removeOpenCollectionListener(); removeOpenWorkspaceListener(); removeWorkspaceConfigUpdatedListener(); diff --git a/packages/bruno-app/src/providers/ReduxStore/index.js b/packages/bruno-app/src/providers/ReduxStore/index.js index 97def82f7..3e17f6ad7 100644 --- a/packages/bruno-app/src/providers/ReduxStore/index.js +++ b/packages/bruno-app/src/providers/ReduxStore/index.js @@ -9,6 +9,7 @@ import globalEnvironmentsReducer from './slices/global-environments'; import logsReducer from './slices/logs'; import performanceReducer from './slices/performance'; import workspacesReducer from './slices/workspaces'; +import apiSpecReducer from './slices/apiSpec'; import { draftDetectMiddleware } from './middlewares/draft/middleware'; import { autosaveMiddleware } from './middlewares/autosave/middleware'; @@ -30,7 +31,8 @@ export const store = configureStore({ globalEnvironments: globalEnvironmentsReducer, logs: logsReducer, performance: performanceReducer, - workspaces: workspacesReducer + workspaces: workspacesReducer, + apiSpec: apiSpecReducer }, middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(middleware) }); diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js b/packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js new file mode 100644 index 000000000..6c951c8a2 --- /dev/null +++ b/packages/bruno-app/src/providers/ReduxStore/slices/apiSpec.js @@ -0,0 +1,162 @@ +import { createSlice } from '@reduxjs/toolkit'; +import { find } from 'lodash'; +import toast from 'react-hot-toast'; + +const initialState = { + apiSpecs: [], + activeApiSpecUid: null +}; + +export const apiSpecSlice = createSlice({ + name: 'apiSpec', + initialState, + reducers: { + apiSpecAddFileEvent: (state, action) => { + const { name, raw, uid, filename, pathname, json } = action?.payload?.data || {}; + if (!uid) { + toast.error('Error adding API spec'); + } + const apiSpec = findApiSpecByUid(state.apiSpecs, uid); + if (apiSpec) { + apiSpec.raw = raw; + apiSpec.name = name; + apiSpec.filename = filename; + apiSpec.pathname = pathname; + apiSpec.json = json; + } else { + const newApiSpec = { + name, + raw, + uid, + filename, + pathname, + json + }; + state.apiSpecs.push(newApiSpec); + } + state.activeApiSpecUid = uid; + }, + apiSpecChangeFileEvent: (state, action) => { + const { name, raw, uid, filename, pathname, json } = action?.payload?.data || {}; + if (!uid) return; + + const apiSpec = findApiSpecByUid(state.apiSpecs, uid); + if (apiSpec) { + apiSpec.raw = raw; + apiSpec.name = name; + apiSpec.filename = filename; + apiSpec.pathname = pathname; + apiSpec.json = json; + } + }, + saveApiSpec: (state, action) => { + const { content, uid } = action.payload; + const apiSpec = findApiSpecByUid(state.apiSpecs, uid); + if (apiSpec) { + apiSpec.raw = content; + } + }, + setActiveApiSpecUid: (state, action) => { + state.activeApiSpecUid = action.payload.uid; + }, + removeApiSpec: (state, action) => { + const { uid } = action.payload; + let apiSpecIndex = state.apiSpecs.findIndex((c) => c.uid == uid); + state.apiSpecs = state.apiSpecs.filter((c) => c.uid !== uid); + let shiftedApiSpec = state.apiSpecs.at(apiSpecIndex); + let lastApiSpec = state.apiSpecs.at(-1); + state.activeApiSpecUid = shiftedApiSpec?.uid || lastApiSpec?.uid || null; + } + } +}); + +export const { apiSpecAddFileEvent, apiSpecChangeFileEvent, saveApiSpec, removeApiSpec, setActiveApiSpecUid } = apiSpecSlice.actions; + +export default apiSpecSlice.reducer; + +const findApiSpecByUid = (apiSpecs, uid) => { + return find(apiSpecs, (apiSpec) => apiSpec.uid === uid); +}; + +export const openApiSpec = (workspacePath = null) => (dispatch, getState) => { + return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + + if (!workspacePath) { + const state = getState(); + const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid); + workspacePath = activeWorkspace?.pathname || null; + } + + ipcRenderer.invoke('renderer:open-api-spec', workspacePath).then(resolve).catch(reject); + }); +}; + +export const saveApiSpecToFile + = ({ uid, content }) => + (dispatch, getState) => { + return new Promise((resolve, reject) => { + const { ipcRenderer } = window; + const state = getState(); + const apiSpec = findApiSpecByUid(state.apiSpec.apiSpecs, uid); + const { pathname } = apiSpec; + ipcRenderer + .invoke('renderer:save-api-spec', pathname, content) + .then(() => { + dispatch(saveApiSpec({ content, uid })); + toast.success('Saved API spec successfully!'); + resolve(); + }) + .catch((reject) => { + toast.error('Error saving file'); + resolve(); + }); + }); + }; + +export const createApiSpecFile = (apiSpecName, apiSpecLocation, content, workspacePath = null) => (dispatch, getState) => { + const { ipcRenderer } = window; + + if (!workspacePath) { + const state = getState(); + const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid); + workspacePath = activeWorkspace?.pathname || null; + } + + return new Promise((resolve, reject) => { + ipcRenderer.invoke('renderer:create-api-spec', apiSpecName, apiSpecLocation, content, workspacePath).then(resolve).catch(reject); + }); +}; + +export const closeApiSpecFile + = ({ uid }) => + (dispatch, getState) => { + return new Promise((resolve, reject) => { + const state = getState(); + const apiSpec = findApiSpecByUid(state.apiSpec.apiSpecs, uid); + if (!apiSpec) { + return reject(new Error('API Spec not found')); + } + if (apiSpec) { + const { ipcRenderer } = window; + + const activeWorkspace = state.workspaces.workspaces.find((w) => w.uid === state.workspaces.activeWorkspaceUid); + const workspacePath = activeWorkspace?.pathname || null; + + ipcRenderer + .invoke('renderer:remove-api-spec', apiSpec.pathname, workspacePath) + .then(async () => { + dispatch(removeApiSpec({ uid })); + + if (activeWorkspace) { + const { loadWorkspaceApiSpecs } = require('./workspaces/actions'); + await dispatch(loadWorkspaceApiSpecs(activeWorkspace.uid)); + } + + resolve(); + }) + .catch((error) => reject(error)); + } + return; + }); + }; diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/app.js b/packages/bruno-app/src/providers/ReduxStore/slices/app.js index d10f8c302..576517597 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/app.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/app.js @@ -10,6 +10,7 @@ const initialState = { screenWidth: 500, showHomePage: false, showPreferences: false, + showApiSpecPage: false, isEnvironmentSettingsModalOpen: false, isGlobalEnvironmentSettingsModalOpen: false, preferences: { @@ -72,10 +73,19 @@ export const appSlice = createSlice({ }, showHomePage: (state) => { state.showHomePage = true; + state.showApiSpecPage = false; }, hideHomePage: (state) => { state.showHomePage = false; }, + showApiSpecPage: (state) => { + state.showHomePage = false; + state.showPreferences = false; + state.showApiSpecPage = true; + }, + hideApiSpecPage: (state) => { + state.showApiSpecPage = false; + }, showPreferences: (state, action) => { state.showPreferences = action.payload; }, @@ -122,6 +132,8 @@ export const { updateGlobalEnvironmentSettingsModalVisibility, showHomePage, hideHomePage, + showApiSpecPage, + hideApiSpecPage, showPreferences, updatePreferences, updateCookies, diff --git a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js index 8944e0a28..d130cd379 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js @@ -183,11 +183,47 @@ const loadWorkspaceCollectionsForSwitch = async (dispatch, workspace) => { await openCollectionsFunction(collectionPaths, updatedWorkspace.pathname); } } + + // Load API specs for this workspace + await dispatch(loadWorkspaceApiSpecs(workspace.uid)); } catch (error) { console.error('Failed to load workspace collections:', error); } }; +export const loadWorkspaceApiSpecs = (workspaceUid) => { + return async (dispatch, getState) => { + try { + const workspace = getState().workspaces.workspaces.find((w) => w.uid === workspaceUid); + if (!workspace || !workspace.pathname) { + return; + } + + const apiSpecs = await ipcRenderer.invoke('renderer:load-workspace-apispecs', workspace.pathname); + + dispatch(updateWorkspace({ + uid: workspaceUid, + apiSpecs: apiSpecs + })); + + const allApiSpecs = getState().apiSpec.apiSpecs; + const alreadyOpenApiSpecs = allApiSpecs.map((a) => a.pathname); + + for (const apiSpec of apiSpecs) { + if (apiSpec.path && !alreadyOpenApiSpecs.includes(apiSpec.path)) { + try { + await ipcRenderer.invoke('renderer:open-api-spec-file', apiSpec.path, workspace.pathname); + } catch (error) { + console.error('Error opening API spec:', error); + } + } + } + } catch (error) { + console.error('Error loading workspace API specs:', error); + } + }; +}; + export const switchWorkspace = (workspaceUid) => { return async (dispatch, getState) => { dispatch(setActiveWorkspace(workspaceUid)); @@ -337,7 +373,7 @@ export const workspaceConfigUpdatedEvent = (workspacePath, workspaceUid, workspa return; } - const { collections, ...configWithoutCollections } = workspaceConfig; + const { collections, apiSpecs, ...configWithoutCollections } = workspaceConfig; dispatch(updateWorkspace({ uid: workspaceUid, @@ -364,6 +400,9 @@ export const workspaceConfigUpdatedEvent = (workspacePath, workspaceUid, workspa } } } + + // Load API specs when workspace config is updated + await dispatch(loadWorkspaceApiSpecs(workspaceUid)); } catch (error) { } } diff --git a/packages/bruno-app/src/utils/exporters/openapi-spec.js b/packages/bruno-app/src/utils/exporters/openapi-spec.js new file mode 100644 index 000000000..bdb8150eb --- /dev/null +++ b/packages/bruno-app/src/utils/exporters/openapi-spec.js @@ -0,0 +1,427 @@ +import jsyaml from 'js-yaml'; +import { interpolate } from '@usebruno/common'; +import { isValidUrl } from 'utils/url/index'; +const xml2js = require('xml2js'); + +export const exportApiSpec = ({ variables, items, name }) => { + items = items.filter((item) => !['grpc-request'].includes(item.type)); + + const components = { + schemas: {}, + requestBodies: {}, + securitySchemes: {} + }; + + const servers = []; + const warnings = []; + + const addWarning = (message, itemName) => { + warnings.push({ + message, + itemName + }); + }; + + const addUrlToServersList = (url) => { + if (!servers?.find((s) => s?.url === url)) { + servers.push({ url }); + } + }; + + const extractTagFromDepth = (item) => { + const { pathname, depth } = item; + if (!pathname) return; + + const parts = pathname.split('\\'); + const baseDepth = parts.length - depth; + if (depth === 1) return ''; + + const tagIndex = Math.max(baseDepth, 0); + + return parts[tagIndex]; + }; + + const generatePaths = () => { + const _items = items.map((item) => { + let url = interpolate(item?.request?.url, variables); + if (isValidUrl(url)) { + let urlDetails = new URL(url); + urlDetails?.pathname && (url = urlDetails?.pathname); + urlDetails?.origin && addUrlToServersList(urlDetails?.origin); + } + const { request } = item; + const { method, params = [], headers = [], body, auth } = request || {}; + + // PARAMS + + const pathParamsRegex = /(? ({ + name: param?.name, + in: 'query', + description: '', + required: param?.enabled, + example: param?.value + })), + ...headers?.map((header) => ({ + name: header?.name, + in: 'header', + description: '', + required: header?.enabled, + example: header?.value + })), + ...pathMatches?.map((path) => ({ + name: path.slice(1, path.length - 1), + in: 'path', + required: true + })) + ]; + + const pathBody = { + summary: item?.name, + operationId: item?.name, + description: '', + tags: [extractTagFromDepth(item)], + responses: { + 200: { + description: '' + } + } + }; + + if (parameters?.length) { + pathBody['parameters'] = parameters; + } + + // BODY + + let schemaId = `${item?.name?.split(' ').join('_').toLowerCase()}`; + let securitySchemaId = `${item?.name?.split(' ').join('_').toLowerCase()}`; + let requestBodyId = `${item?.name?.split(' ').join('_').toLowerCase()}`; + if (body?.mode) { + switch (body?.mode) { + case 'json': + if (!body?.json) break; + try { + const parsedJson = JSON.parse(body.json); + components.schemas[schemaId] = generateProperyShape(parsedJson); + components.requestBodies[requestBodyId] = { + content: { + 'application/json': { + schema: { + $ref: `#/components/schemas/${schemaId}` + } + } + }, + description: '', + required: true + }; + pathBody['requestBody'] = { + $ref: `#/components/requestBodies/${requestBodyId}` + }; + } catch (error) { + addWarning(`Failed to parse JSON in request body: ${error.message}`, item?.name); + components.schemas[schemaId] = { + type: 'object', + properties: {} + }; + components.requestBodies[requestBodyId] = { + content: { + 'application/json': { + schema: { + $ref: `#/components/schemas/${schemaId}` + } + } + }, + description: '', + required: true + }; + pathBody['requestBody'] = { + $ref: `#/components/requestBodies/${requestBodyId}` + }; + } + break; + case 'xml': + if (!body?.xml) break; + try { + const jsonResult = xmlToJson(body?.xml); + if (!jsonResult) { + addWarning('Failed to parse XML in request body', item?.name); + break; + } + components.schemas[schemaId] = generateProperyShape(jsonResult); + components.requestBodies[requestBodyId] = { + content: { + 'application/xml': { + schema: { + $ref: `#/components/schemas/${schemaId}` + } + } + }, + description: '', + required: true + }; + pathBody['requestBody'] = { + $ref: `#/components/requestBodies/${requestBodyId}` + }; + } catch (error) { + addWarning(`Failed to parse XML in request body: ${error.message}`, item?.name); + } + break; + case 'multipartForm': + if (!body?.multipartForm) return; + let multipartFormToKeyValue = body?.multipartForm.reduce((acc, f) => { + acc[f?.name] = f.value; + return acc; + }, {}); + components.schemas[schemaId] = generateProperyShape(multipartFormToKeyValue); + components.requestBodies[requestBodyId] = { + content: { + 'multipart/form-data:': { + schema: { + $ref: `#/components/schemas/${schemaId}` + } + } + }, + description: '', + required: true + }; + pathBody['requestBody'] = { + $ref: `#/components/requestBodies/${requestBodyId}` + }; + case 'formUrlEncoded': + if (!body?.formUrlEncoded) return; + let formUrlEncodedToKeyValue = body?.formUrlEncoded.reduce((acc, f) => { + acc[f?.name] = f.value; + return acc; + }, {}); + components.schemas[schemaId] = generateProperyShape(formUrlEncodedToKeyValue); + components.requestBodies[requestBodyId] = { + content: { + 'application/x-www-form-urlencoded:': { + schema: { + $ref: `#/components/schemas/${schemaId}` + } + } + }, + description: '', + required: true + }; + pathBody['requestBody'] = { + $ref: `#/components/requestBodies/${requestBodyId}` + }; + case 'text': + if (!body?.text) return; + pathBody['requestBody'] = { + content: { + 'text/plain': { + schema: { + type: 'string' + } + } + } + }; + default: + break; + } + } + + // AUTH + + if (auth?.mode) { + switch (auth?.mode) { + case 'basic': + components.securitySchemes[securitySchemaId] = { + type: 'http', + scheme: 'basic' + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + case 'bearer': + components.securitySchemes[securitySchemaId] = { + type: 'http', + scheme: 'bearer' + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + case 'oauth2': + if (!auth?.oauth2?.grantType) break; + const { authorizationUrl, accessTokenUrl, callbackUrl, scope } = auth?.oauth2; + switch (auth?.oauth2?.grantType) { + case 'authorization_code': + components.securitySchemes[securitySchemaId] = { + type: 'oauth2', + flows: { + authorizationCode: { + authorizationUrl, + tokenUrl: accessTokenUrl, + ...(scope.length > 0 + ? { + scopes: { + [scope]: '' + } + } + : {}) + } + } + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + case 'password': + components.securitySchemes[securitySchemaId] = { + type: 'oauth2', + flows: { + password: { + tokenUrl: accessTokenUrl, + ...(scope.length > 0 + ? { + scopes: { + [scope]: '' + } + } + : {}) + } + } + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + case 'client_credentials': + components.securitySchemes[securitySchemaId] = { + type: 'oauth2', + flows: { + password: { + tokenUrl: accessTokenUrl, + ...(scope.length > 0 + ? { + scopes: { + [scope]: '' + } + } + : {}) + } + } + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + } + break; + case 'awsv4': + components.securitySchemes[securitySchemaId] = { + 'type': 'apiKey', + 'name': 'Authorization', + 'in': 'header', + 'x-amazon-apigateway-authtype': 'awsSigv4' + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + case 'digest': + components.securitySchemes[securitySchemaId] = { + type: 'digest', + scheme: 'digest', + description: 'Digest Authentication' + }; + pathBody['security'] = { + [securitySchemaId]: [] + }; + break; + default: + break; + } + } + + return { + url, + method: method.toLowerCase(), + data: pathBody + }; + }); + + return _items.reduce((acc, item) => { + if (!acc[item?.url]) { + acc[item?.url] = {}; + } + acc[item?.url][item?.method] = item?.data; + return acc; + }, {}); + }; + + const collectionToExport = {}; + collectionToExport.openapi = '3.0.0'; + collectionToExport.info = generateInfoSection(name); + collectionToExport.paths = generatePaths(); + collectionToExport.servers = servers; + collectionToExport.components = components; + + let yaml = jsyaml.dump(collectionToExport); + + return { + content: yaml, + warnings + }; +}; + +const xmlToJson = (xmlString) => { + const parser = new xml2js.Parser({ explicitArray: false, trim: true }); + let jsonResult = null; + + parser.parseString(xmlString, (err, result) => { + if (err) { + throw err; + } else { + jsonResult = result; + } + }); + + return jsonResult; +}; + +const generateInfoSection = (name) => { + return { + title: name, + version: '1.0.0' + }; +}; + +const generateProperyShape = (obj) => { + let data = {}; + + // add 'type' + if (Array.isArray(obj)) { + data['type'] = 'array'; + data['items'] = { + type: 'string' + }; + } else { + data['type'] = typeof obj; + } + + // add 'properties' + let properties = null; + if (obj && typeof obj == 'object') { + properties = {}; + let keys = Object.keys(obj); + keys.forEach((key) => { + let value = obj[key]; + properties[key] = generateProperyShape(value); + }); + if (keys.length) { + data['properties'] = properties; + } + } + return data; +}; diff --git a/packages/bruno-electron/src/app/apiSpecs.js b/packages/bruno-electron/src/app/apiSpecs.js new file mode 100644 index 000000000..1e5c538dd --- /dev/null +++ b/packages/bruno-electron/src/app/apiSpecs.js @@ -0,0 +1,110 @@ +const { dialog, ipcMain } = require('electron'); +const { isDirectory, normalizeAndResolvePath } = require('../utils/filesystem'); +const { generateUidBasedOnHash } = require('../utils/common'); + +const openApiSpecDialog = async (win, watcher, options = {}) => { + const { filePaths } = await dialog.showOpenDialog(win, { + properties: ['openFile', 'createFile'] + }); + + if (filePaths && filePaths[0]) { + const resolvedPath = normalizeAndResolvePath(filePaths[0]); + try { + openApiSpec(win, watcher, resolvedPath, options); + } catch (err) { + console.error(`[ERROR] Cannot open API spec: "${resolvedPath}"`); + } + } +}; + +const openApiSpec = async (win, watcher, apiSpecPath, options = {}) => { + try { + const uid = generateUidBasedOnHash(apiSpecPath); + + if (options.workspacePath) { + const fs = require('fs'); + const path = require('path'); + const yaml = require('js-yaml'); + + const workspaceFilePath = path.join(options.workspacePath, 'workspace.yml'); + + if (fs.existsSync(workspaceFilePath)) { + const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8'); + const workspaceConfig = yaml.load(yamlContent); + + workspaceConfig.apiSpecs = workspaceConfig.apiSpecs || []; + + let relativePath = apiSpecPath; + try { + const relPath = path.relative(options.workspacePath, apiSpecPath); + if (!relPath.startsWith('..') && !path.isAbsolute(relPath)) { + relativePath = relPath; + } + } catch (error) { + console.log('Using absolute path for API spec:', error.message); + } + + const apiSpecName = path.basename(apiSpecPath, path.extname(apiSpecPath)); + const apiSpecEntry = { + name: apiSpecName, + path: relativePath + }; + + const existingApiSpec = workspaceConfig.apiSpecs.find((a) => { + const existingPath = path.isAbsolute(a.path) + ? a.path + : path.resolve(options.workspacePath, a.path); + return existingPath === apiSpecPath || a.name === apiSpecName; + }); + + if (!existingApiSpec) { + workspaceConfig.apiSpecs.push(apiSpecEntry); + + const updatedYamlContent = yaml.dump(workspaceConfig, { + indent: 2, + lineWidth: -1, + noRefs: true + }); + fs.writeFileSync(workspaceFilePath, updatedYamlContent); + + // Notify frontend that workspace config was updated + const workspaceUid = generateUidBasedOnHash(options.workspacePath); + win.webContents.send('main:workspace-config-updated', options.workspacePath, workspaceUid, workspaceConfig); + } + } + } + + if (!watcher.hasWatcher(apiSpecPath)) { + ipcMain.emit('main:apispec-opened', win, apiSpecPath, uid, options.workspacePath); + } else { + win.webContents.send('main:apispec-tree-updated', 'addFile', { + pathname: apiSpecPath, + uid: uid, + raw: require('fs').readFileSync(apiSpecPath, 'utf8'), + name: require('path').basename(apiSpecPath, require('path').extname(apiSpecPath)), + filename: require('path').basename(apiSpecPath), + json: (() => { + const ext = require('path').extname(apiSpecPath).toLowerCase(); + const content = require('fs').readFileSync(apiSpecPath, 'utf8'); + if (ext === '.yaml' || ext === '.yml') { + return require('js-yaml').load(content); + } else if (ext === '.json') { + return JSON.parse(content); + } + return null; + })() + }); + } + } catch (err) { + if (!options.dontSendDisplayErrors) { + win.webContents.send('main:display-error', { + error: err.message || 'An error occurred while opening the apiSpec' + }); + } + } +}; + +module.exports = { + openApiSpec, + openApiSpecDialog +}; diff --git a/packages/bruno-electron/src/app/apiSpecsWatcher.js b/packages/bruno-electron/src/app/apiSpecsWatcher.js new file mode 100644 index 000000000..73f210cd0 --- /dev/null +++ b/packages/bruno-electron/src/app/apiSpecsWatcher.js @@ -0,0 +1,135 @@ +const _ = require('lodash'); +const fs = require('fs'); +const path = require('path'); +const chokidar = require('chokidar'); +const { getApiSpecUid } = require('../cache/apiSpecUids'); +const yaml = require('js-yaml'); +const { isDirectory } = require('../utils/filesystem'); +const { safeParseJSON } = require('../utils/common'); + +const hasApiSpecExtension = (filename) => { + if (!filename || typeof filename !== 'string') return false; + return ['yaml', 'yml', 'json'].some((ext) => filename.toLowerCase().endsWith(`.${ext}`)); +}; + +const parseApiSpecContent = (pathname) => { + const extension = path.extname(pathname).toLowerCase(); + let content = fs.readFileSync(pathname, 'utf8'); + + if (extension === '.yaml' || extension === '.yml') { + return yaml.load(content); + } else if (extension === '.json') { + return safeParseJSON(content); + } + return null; +}; + +const hydrateApiSpecWithUuid = (apiSpec, pathname) => { + apiSpec.uid = getApiSpecUid(pathname); + return apiSpec; +}; + +const add = async (win, pathname) => { + if (!hasApiSpecExtension(pathname)) return; + try { + const basename = path.basename(pathname); + const file = {}; + const apiSpecContent = parseApiSpecContent(pathname); + + file.raw = fs.readFileSync(pathname, 'utf8'); + file.name = apiSpecContent?.info?.title || basename.split('.')[0]; + file.filename = basename; + file.pathname = pathname; + file.json = apiSpecContent; + hydrateApiSpecWithUuid(file, pathname); + win.webContents.send('main:apispec-tree-updated', 'addFile', file); + } catch (err) { + console.error(err); + } +}; + +const change = async (win, pathname) => { + if (!hasApiSpecExtension(pathname)) return; + try { + const basename = path.basename(pathname); + const file = {}; + const apiSpecContent = parseApiSpecContent(pathname); + + file.raw = fs.readFileSync(pathname, 'utf8'); + file.name = apiSpecContent?.info?.title || basename.split('.')[0]; + file.filename = basename; + file.pathname = pathname; + file.json = apiSpecContent; + hydrateApiSpecWithUuid(file, pathname); + win.webContents.send('main:apispec-tree-updated', 'changeFile', file); + } catch (err) { + console.error(err); + } +}; + +class ApiSpecWatcher { + constructor() { + this.watchers = {}; + this.watcherWorkspaces = {}; + } + + addWatcher(win, watchPath, apiSpecUid, brunoConfig, workspacePath = null) { + // Avoid creating watcher for directories + if (isDirectory(watchPath)) return; + + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + } + + if (workspacePath) { + this.watcherWorkspaces[watchPath] = workspacePath; + } + + const ignores = brunoConfig?.ignore || []; + const self = this; + setTimeout(() => { + const watcher = chokidar.watch(watchPath, { + ignoreInitial: false, + usePolling: watchPath.startsWith('\\\\') ? true : false, + ignored: (filepath) => { + const normalizedPath = filepath.replace(/\\/g, '/'); + const relativePath = path.relative(watchPath, normalizedPath); + + return ignores.some((ignorePattern) => { + const normalizedIgnorePattern = ignorePattern.replace(/\\/g, '/'); + return relativePath === normalizedIgnorePattern || relativePath.startsWith(normalizedIgnorePattern); + }); + }, + persistent: true, + ignorePermissionErrors: true, + awaitWriteFinish: { + stabilityThreshold: 80, + pollInterval: 10 + }, + depth: 20 + }); + + watcher + .on('add', (pathname) => add(win, pathname, apiSpecUid, watchPath, workspacePath)) + .on('change', (pathname) => change(win, pathname, apiSpecUid, watchPath, workspacePath)); + + self.watchers[watchPath] = watcher; + }, 100); + } + + hasWatcher(watchPath) { + return this.watchers[watchPath]; + } + + removeWatcher(watchPath, win) { + if (this.watchers[watchPath]) { + this.watchers[watchPath].close(); + this.watchers[watchPath] = null; + } + if (this.watcherWorkspaces[watchPath]) { + delete this.watcherWorkspaces[watchPath]; + } + } +} + +module.exports = ApiSpecWatcher; diff --git a/packages/bruno-electron/src/cache/apiSpecUids.js b/packages/bruno-electron/src/cache/apiSpecUids.js new file mode 100644 index 000000000..14b26540c --- /dev/null +++ b/packages/bruno-electron/src/cache/apiSpecUids.js @@ -0,0 +1,44 @@ +/** + * we maintain a cache of apiSpec uids to ensure that we + * preserve the same uid for a apiSpec even when the apiSpec + * moves to a different location + * + * In the past, we used to generate unique ids based on the + * pathname of the apiSpec, but we faced problems when implementing + * functionality where the user can move the apiSpec to a different + * location. In that case, the uid would change, and the we would + * lose the apiSpec's draft state if the user has made some changes + */ + +const apiSpecUids = new Map(); +const { uuid } = require('../utils/common'); + +const getApiSpecUid = (pathname) => { + let uid = apiSpecUids.get(pathname); + + if (!uid) { + uid = uuid(); + apiSpecUids.set(pathname, uid); + } + + return uid; +}; + +const moveApiSpecUid = (oldPathname, newPathname) => { + const uid = apiSpecUids.get(oldPathname); + + if (uid) { + apiSpecUids.delete(oldPathname); + apiSpecUids.set(newPathname, uid); + } +}; + +const removeApiSpecUid = (pathname) => { + apiSpecUids.delete(pathname); +}; + +module.exports = { + getApiSpecUid, + moveApiSpecUid, + removeApiSpecUid +}; diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 5eeb96e18..97d3b3c77 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -39,8 +39,10 @@ const registerFilesystemIpc = require('./ipc/filesystem'); const registerPreferencesIpc = require('./ipc/preferences'); const registerSystemMonitorIpc = require('./ipc/system-monitor'); const registerWorkspaceIpc = require('./ipc/workspace'); +const registerApiSpecIpc = require('./ipc/apiSpec'); const collectionWatcher = require('./app/collection-watcher'); const WorkspaceWatcher = require('./app/workspace-watcher'); +const ApiSpecWatcher = require('./app/apiSpecsWatcher'); const { loadWindowState, saveBounds, saveMaximized } = require('./utils/window'); const { globalEnvironmentsManager } = require('./store/workspace-environments'); const registerNotificationsIpc = require('./ipc/notifications'); @@ -58,6 +60,7 @@ const systemMonitor = new SystemMonitor(); const terminalManager = new TerminalManager(); const workspaceWatcher = new WorkspaceWatcher(); +const apiSpecWatcher = new ApiSpecWatcher(); // Reference: https://content-security-policy.com/ const contentSecurityPolicy = [ @@ -238,6 +241,7 @@ app.on('ready', async () => { registerCollectionsIpc(mainWindow, collectionWatcher); registerPreferencesIpc(mainWindow, collectionWatcher); registerWorkspaceIpc(mainWindow, workspaceWatcher); + registerApiSpecIpc(mainWindow, apiSpecWatcher); registerNotificationsIpc(mainWindow, collectionWatcher); registerFilesystemIpc(mainWindow); registerSystemMonitorIpc(mainWindow, systemMonitor); diff --git a/packages/bruno-electron/src/ipc/apiSpec.js b/packages/bruno-electron/src/ipc/apiSpec.js new file mode 100644 index 000000000..174a67666 --- /dev/null +++ b/packages/bruno-electron/src/ipc/apiSpec.js @@ -0,0 +1,122 @@ +const { ipcMain } = require('electron'); +const { openApiSpecDialog, openApiSpec } = require('../app/apiSpecs'); +const { writeFile } = require('../utils/filesystem'); +const { removeApiSpecUid } = require('../cache/apiSpecUids'); +const path = require('path'); +const fs = require('fs'); + +const registerRendererEventHandlers = (mainWindow, watcher, lastOpenedApiSpecs) => { + ipcMain.handle('renderer:open-api-spec', (event, workspacePath = null) => { + if (watcher && mainWindow) { + openApiSpecDialog(mainWindow, watcher, { workspacePath }); + } + }); + + ipcMain.handle('renderer:open-api-spec-file', (event, apiSpecPath, workspacePath = null) => { + if (watcher && mainWindow) { + openApiSpec(mainWindow, watcher, apiSpecPath, { workspacePath }); + } + }); + + ipcMain.handle('renderer:save-api-spec', async (event, pathname, content) => { + try { + await writeFile(pathname, content); + Promise.resolve(); + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:create-api-spec', async (event, apiSpecName, apiSpecLocation, content = '', workspacePath = null) => { + try { + let pathname = path.join(apiSpecLocation, apiSpecName); + if (fs.existsSync(pathname)) { + throw new Error(`path: ${pathname} already exists`); + } + await writeFile(pathname, content); + openApiSpec(mainWindow, watcher, pathname, { workspacePath }); + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:remove-api-spec', async (event, pathname, workspacePath = null) => { + try { + if (watcher && mainWindow) { + watcher.removeWatcher(pathname, mainWindow); + removeApiSpecUid(pathname); + + if (workspacePath) { + const yaml = require('js-yaml'); + const workspaceFilePath = path.join(workspacePath, 'workspace.yml'); + + if (fs.existsSync(workspaceFilePath)) { + const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8'); + const workspaceConfig = yaml.load(yamlContent); + + if (workspaceConfig.apiSpecs) { + workspaceConfig.apiSpecs = workspaceConfig.apiSpecs.filter((a) => { + const apiSpecPathFromYml = a.path; + if (!apiSpecPathFromYml) return true; + + const absoluteApiSpecPath = path.isAbsolute(apiSpecPathFromYml) + ? apiSpecPathFromYml + : path.resolve(workspacePath, apiSpecPathFromYml); + + return absoluteApiSpecPath !== pathname; + }); + + const updatedYamlContent = yaml.dump(workspaceConfig, { + indent: 2, + lineWidth: -1, + noRefs: true + }); + fs.writeFileSync(workspaceFilePath, updatedYamlContent); + } + } + } + } + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:fetch-api-spec', async (event, url) => { + try { + const data = await fetch(url).then((res) => res.text()); + return data; + } catch (error) { + return Promise.reject(error); + } + }); + + ipcMain.handle('renderer:ensure-apispec-folder', async (event, workspacePath) => { + try { + const apiSpecPath = path.join(workspacePath, 'apispec'); + if (!fs.existsSync(apiSpecPath)) { + fs.mkdirSync(apiSpecPath, { recursive: true }); + } + return apiSpecPath; + } catch (error) { + return Promise.reject(error); + } + }); +}; + +const registerMainEventHandlers = (mainWindow, watcher, lastOpenedApiSpecs) => { + ipcMain.handle('main:open-api-spec', () => { + if (watcher && mainWindow) { + openApiSpecDialog(mainWindow, watcher); + } + }); + ipcMain.on('main:apispec-opened', (win, pathname, uid, workspacePath = null) => { + watcher.addWatcher(win, pathname, uid, {}, workspacePath); + }); +}; + +const registerApiSpecIpc = (mainWindow, watcher, lastOpenedApiSpecs) => { + registerRendererEventHandlers(mainWindow, watcher, lastOpenedApiSpecs); + registerMainEventHandlers(mainWindow, watcher, lastOpenedApiSpecs); +}; + +module.exports = registerApiSpecIpc; diff --git a/packages/bruno-electron/src/ipc/collection.js b/packages/bruno-electron/src/ipc/collection.js index 1de0989c3..a761b2370 100644 --- a/packages/bruno-electron/src/ipc/collection.js +++ b/packages/bruno-electron/src/ipc/collection.js @@ -13,8 +13,10 @@ const { stringifyCollection, parseFolder, stringifyFolder, - stringifyEnvironment + stringifyEnvironment, + parseEnvironment } = require('@usebruno/filestore'); +const { dotenvToJson } = require('@usebruno/lang'); const brunoConverters = require('@usebruno/converters'); const { postmanToBruno } = brunoConverters; const { cookiesStore } = require('../store/cookies'); @@ -41,7 +43,11 @@ const { copyPath, removePath, getPaths, - generateUniqueName + generateUniqueName, + isDotEnvFile, + isBrunoConfigFile, + isBruEnvironmentConfig, + isCollectionRootBruFile } = require('../utils/filesystem'); const { openCollectionDialog, openCollectionsByPathname } = require('../app/collections'); const { generateUidBasedOnHash, stringifyJson, safeStringifyJSON, safeParseJSON } = require('../utils/common'); @@ -1540,6 +1546,116 @@ const registerRendererEventHandlers = (mainWindow, watcher) => { return Promise.reject(error); } }); + + ipcMain.handle('renderer:get-collection-json', async (event, collectionPath) => { + let variables = {}; + let name = ''; + const getBruFilesRecursively = async (dir) => { + const getFilesInOrder = async (dir) => { + let bruJsons = []; + + const traverse = async (currentPath) => { + const filesInCurrentDir = fs.readdirSync(currentPath); + + if (currentPath.includes('node_modules')) { + return; + } + + for (const file of filesInCurrentDir) { + const filePath = path.join(currentPath, file); + const stats = fs.lstatSync(filePath); + + if (stats.isDirectory() && !filePath.startsWith('.git') && !filePath.startsWith('node_modules')) { + await traverse(filePath); + } + } + + const currentDirBruJsons = []; + for (const file of filesInCurrentDir) { + const filePath = path.join(currentPath, file); + const stats = fs.lstatSync(filePath); + + if (isBrunoConfigFile(filePath, collectionPath)) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const brunoConfig = JSON.parse(content); + + name = brunoConfig?.name; + } catch (err) { + console.error(err); + } + } + + if (isDotEnvFile(filePath, collectionPath)) { + try { + const content = fs.readFileSync(filePath, 'utf8'); + const jsonData = dotenvToJson(content); + variables = { + ...variables, + processEnvVariables: { + ...process.env, + ...jsonData + } + }; + continue; + } catch (err) { + console.error(err); + } + } + + if (isBruEnvironmentConfig(filePath, collectionPath)) { + try { + let bruContent = fs.readFileSync(filePath, 'utf8'); + const environmentFilepathBasename = path.basename(filePath); + const environmentName = environmentFilepathBasename.substring(0, environmentFilepathBasename.length - 4); + let data = await parseEnvironment(bruContent); + variables = { + ...variables, + envVariables: { + ...(variables?.envVariables || {}), + [path.basename(filePath)]: data.variables + } + }; + continue; + } catch (err) { + console.error(err); + } + } + + if (isCollectionRootBruFile(filePath, collectionPath)) { + try { + let bruContent = fs.readFileSync(filePath, 'utf8'); + let data = await parseCollection(bruContent); + // TODO + continue; + } catch (err) { + console.error(err); + } + } + if (!stats.isDirectory() && path.extname(filePath) === '.bru' && file !== 'folder.bru') { + const bruContent = fs.readFileSync(filePath, 'utf8'); + const bruJson = parseRequest(bruContent); + + currentDirBruJsons.push({ + ...bruJson + }); + } + } + + bruJsons = bruJsons.concat(currentDirBruJsons); + }; + + await traverse(dir); + return bruJsons; + }; + + const orderedFiles = await getFilesInOrder(dir); + return orderedFiles; + }; + + const files = await getBruFilesRecursively(collectionPath); + return { name, files, ...variables }; + }); }; const registerMainEventHandlers = (mainWindow, watcher) => { diff --git a/packages/bruno-electron/src/ipc/workspace.js b/packages/bruno-electron/src/ipc/workspace.js index 4610516ad..f25ffb909 100644 --- a/packages/bruno-electron/src/ipc/workspace.js +++ b/packages/bruno-electron/src/ipc/workspace.js @@ -150,6 +150,43 @@ const registerWorkspaceIpc = (mainWindow, workspaceWatcher) => { } }); + ipcMain.handle('renderer:load-workspace-apispecs', async (event, workspacePath) => { + try { + if (!workspacePath) { + throw new Error('Workspace path is undefined'); + } + + const workspaceFilePath = path.join(workspacePath, 'workspace.yml'); + + if (!fs.existsSync(workspaceFilePath)) { + throw new Error('Invalid workspace: workspace.yml not found'); + } + + const yamlContent = fs.readFileSync(workspaceFilePath, 'utf8'); + const workspaceConfig = yaml.load(yamlContent); + + if (!workspaceConfig || typeof workspaceConfig !== 'object') { + return []; + } + + const apiSpecs = workspaceConfig.apiSpecs || []; + + const resolvedApiSpecs = apiSpecs.map((apiSpec) => { + if (apiSpec.path && !path.isAbsolute(apiSpec.path)) { + return { + ...apiSpec, + path: path.join(workspacePath, apiSpec.path) + }; + } + return apiSpec; + }); + + return resolvedApiSpecs; + } catch (error) { + throw error; + } + }); + ipcMain.handle('renderer:get-last-opened-workspaces', async () => { try { const workspaces = lastOpenedWorkspaces.getAll(); diff --git a/packages/bruno-electron/src/utils/filesystem.js b/packages/bruno-electron/src/utils/filesystem.js index f6d725e6d..a49d6e7c8 100644 --- a/packages/bruno-electron/src/utils/filesystem.js +++ b/packages/bruno-electron/src/utils/filesystem.js @@ -417,6 +417,35 @@ const isLargeFile = (filePath, threshold = 10 * 1024 * 1024) => { return size > threshold; }; +const isDotEnvFile = (pathname, collectionPath) => { + const dirname = path.dirname(pathname); + const basename = path.basename(pathname); + + return dirname === collectionPath && basename === '.env'; +}; + +const isBrunoConfigFile = (pathname, collectionPath) => { + const dirname = path.dirname(pathname); + const basename = path.basename(pathname); + + return dirname === collectionPath && basename === 'bruno.json'; +}; + +const isBruEnvironmentConfig = (pathname, collectionPath) => { + const dirname = path.dirname(pathname); + const envDirectory = path.join(collectionPath, 'environments'); + const basename = path.basename(pathname); + + 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'; +}; + module.exports = { isValidPathname, exists, @@ -450,5 +479,9 @@ module.exports = { getPaths, isLargeFile, generateUniqueName, - getCollectionFormat + getCollectionFormat, + isDotEnvFile, + isBrunoConfigFile, + isBruEnvironmentConfig, + isCollectionRootBruFile }; diff --git a/packages/bruno-electron/src/utils/workspace-config.js b/packages/bruno-electron/src/utils/workspace-config.js index b6a18c368..ecb6ea5fc 100644 --- a/packages/bruno-electron/src/utils/workspace-config.js +++ b/packages/bruno-electron/src/utils/workspace-config.js @@ -65,7 +65,8 @@ const createWorkspaceConfig = (workspaceName) => ({ type: WORKSPACE_TYPE, version: '1.0.0', docs: '', - collections: [] + collections: [], + apiSpecs: [] }); const readWorkspaceConfig = (workspacePath) => { @@ -211,6 +212,84 @@ const getWorkspaceCollections = (workspacePath) => { }); }; +const getWorkspaceApiSpecs = (workspacePath) => { + const config = readWorkspaceConfig(workspacePath); + const apiSpecs = config.apiSpecs || []; + + // Resolve relative paths to absolute + return apiSpecs.map((apiSpec) => { + if (apiSpec.path && !path.isAbsolute(apiSpec.path)) { + return { + ...apiSpec, + path: path.join(workspacePath, apiSpec.path) + }; + } + return apiSpec; + }); +}; + +const addApiSpecToWorkspace = async (workspacePath, apiSpec) => { + const config = readWorkspaceConfig(workspacePath); + + if (!config.apiSpecs) { + config.apiSpecs = []; + } + + // Normalize API spec entry with relative path + const normalizedApiSpec = { + name: apiSpec.name, + path: makeRelativePath(workspacePath, apiSpec.path) + }; + + // Check if API spec already exists + const existingIndex = config.apiSpecs.findIndex( + (a) => a.name === normalizedApiSpec.name || a.path === normalizedApiSpec.path + ); + + if (existingIndex >= 0) { + config.apiSpecs[existingIndex] = normalizedApiSpec; + } else { + config.apiSpecs.push(normalizedApiSpec); + } + + await writeWorkspaceConfig(workspacePath, config); + return config.apiSpecs; +}; + +const removeApiSpecFromWorkspace = async (workspacePath, apiSpecPath) => { + const config = readWorkspaceConfig(workspacePath); + + if (!config.apiSpecs) { + return { removedApiSpec: null, updatedConfig: config }; + } + + let removedApiSpec = null; + + config.apiSpecs = config.apiSpecs.filter((a) => { + const apiSpecPathFromYml = a.path; + if (!apiSpecPathFromYml) return true; + + // Convert to absolute path for comparison + const absoluteApiSpecPath = path.isAbsolute(apiSpecPathFromYml) + ? apiSpecPathFromYml + : path.resolve(workspacePath, apiSpecPathFromYml); + + if (path.normalize(absoluteApiSpecPath) === path.normalize(apiSpecPath)) { + removedApiSpec = a; + return false; + } + + return true; + }); + + await writeWorkspaceConfig(workspacePath, config); + + return { + removedApiSpec, + updatedConfig: config + }; +}; + module.exports = { makeRelativePath, normalizeCollectionEntry, @@ -224,5 +303,8 @@ module.exports = { updateWorkspaceDocs, addCollectionToWorkspace, removeCollectionFromWorkspace, - getWorkspaceCollections + getWorkspaceCollections, + getWorkspaceApiSpecs, + addApiSpecToWorkspace, + removeApiSpecFromWorkspace };