From 67903f26bc00728ef8531dca0ffe09b68f0f3b04 Mon Sep 17 00:00:00 2001 From: naman-bruno Date: Wed, 24 Dec 2025 22:28:38 +0530 Subject: [PATCH] export & import in opencollection format (#6329) --- package-lock.json | 267 ++++------ .../ShareCollection/StyledWrapper.js | 15 + .../src/components/ShareCollection/index.js | 38 +- .../Sidebar/ImportCollection/index.js | 5 +- .../Sidebar/ImportCollectionLocation/index.js | 6 + .../ReduxStore/slices/workspaces/actions.js | 4 + .../src/utils/exporters/opencollection.js | 23 + .../src/utils/importers/opencollection.js | 81 +++ packages/bruno-converters/package.json | 3 +- packages/bruno-converters/rollup.config.js | 86 +++- packages/bruno-converters/src/index.js | 2 + .../opencollection/bruno-to-opencollection.ts | 172 +++++++ .../src/opencollection/common/actions.ts | 83 ++++ .../src/opencollection/common/assertions.ts | 48 ++ .../src/opencollection/common/auth.ts | 470 ++++++++++++++++++ .../src/opencollection/common/body.ts | 227 +++++++++ .../src/opencollection/common/headers.ts | 44 ++ .../src/opencollection/common/index.ts | 8 + .../src/opencollection/common/params.ts | 47 ++ .../src/opencollection/common/scripts.ts | 64 +++ .../src/opencollection/common/variables.ts | 75 +++ .../src/opencollection/environment.ts | 79 +++ .../src/opencollection/folder.ts | 135 +++++ .../src/opencollection/index.ts | 37 ++ .../src/opencollection/items/graphql.ts | 192 +++++++ .../src/opencollection/items/grpc.ts | 214 ++++++++ .../src/opencollection/items/http.ts | 295 +++++++++++ .../src/opencollection/items/index.ts | 125 +++++ .../src/opencollection/items/websocket.ts | 176 +++++++ .../opencollection/opencollection-to-bruno.ts | 133 +++++ .../src/opencollection/types.ts | 208 ++++++++ .../src/wsdl/wsdl-to-bruno.js | 2 +- packages/bruno-converters/tsconfig.json | 38 +- 33 files changed, 3180 insertions(+), 222 deletions(-) create mode 100644 packages/bruno-app/src/utils/exporters/opencollection.js create mode 100644 packages/bruno-app/src/utils/importers/opencollection.js create mode 100644 packages/bruno-converters/src/opencollection/bruno-to-opencollection.ts create mode 100644 packages/bruno-converters/src/opencollection/common/actions.ts create mode 100644 packages/bruno-converters/src/opencollection/common/assertions.ts create mode 100644 packages/bruno-converters/src/opencollection/common/auth.ts create mode 100644 packages/bruno-converters/src/opencollection/common/body.ts create mode 100644 packages/bruno-converters/src/opencollection/common/headers.ts create mode 100644 packages/bruno-converters/src/opencollection/common/index.ts create mode 100644 packages/bruno-converters/src/opencollection/common/params.ts create mode 100644 packages/bruno-converters/src/opencollection/common/scripts.ts create mode 100644 packages/bruno-converters/src/opencollection/common/variables.ts create mode 100644 packages/bruno-converters/src/opencollection/environment.ts create mode 100644 packages/bruno-converters/src/opencollection/folder.ts create mode 100644 packages/bruno-converters/src/opencollection/index.ts create mode 100644 packages/bruno-converters/src/opencollection/items/graphql.ts create mode 100644 packages/bruno-converters/src/opencollection/items/grpc.ts create mode 100644 packages/bruno-converters/src/opencollection/items/http.ts create mode 100644 packages/bruno-converters/src/opencollection/items/index.ts create mode 100644 packages/bruno-converters/src/opencollection/items/websocket.ts create mode 100644 packages/bruno-converters/src/opencollection/opencollection-to-bruno.ts create mode 100644 packages/bruno-converters/src/opencollection/types.ts diff --git a/package-lock.json b/package-lock.json index e523ba753..3cd154b5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1604,7 +1604,7 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz", "integrity": "sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", @@ -1622,7 +1622,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz", "integrity": "sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.22.6", @@ -1639,7 +1639,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -1657,7 +1657,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/@babel/helper-globals": { @@ -1737,7 +1737,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz", "integrity": "sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", @@ -1812,7 +1812,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz", "integrity": "sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/template": "^7.25.9", @@ -1855,7 +1855,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz", "integrity": "sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -1872,7 +1872,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz", "integrity": "sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -1888,7 +1888,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz", "integrity": "sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -1904,7 +1904,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz", "integrity": "sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -1922,7 +1922,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz", "integrity": "sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -1957,7 +1957,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2056,7 +2056,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz", "integrity": "sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2072,7 +2072,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz", "integrity": "sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2254,7 +2254,7 @@ "version": "7.18.6", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.18.6", @@ -2271,7 +2271,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz", "integrity": "sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2287,7 +2287,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz", "integrity": "sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -2305,7 +2305,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz", "integrity": "sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.25.9", @@ -2323,7 +2323,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.25.9.tgz", "integrity": "sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2339,7 +2339,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz", "integrity": "sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2371,7 +2371,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz", "integrity": "sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.25.9", @@ -2388,7 +2388,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz", "integrity": "sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", @@ -2409,7 +2409,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -2419,7 +2419,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz", "integrity": "sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -2436,7 +2436,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz", "integrity": "sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2452,7 +2452,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz", "integrity": "sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -2469,7 +2469,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz", "integrity": "sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2485,7 +2485,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz", "integrity": "sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -2502,7 +2502,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz", "integrity": "sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2518,7 +2518,7 @@ "version": "7.26.3", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz", "integrity": "sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2534,7 +2534,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz", "integrity": "sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2566,7 +2566,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz", "integrity": "sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -2583,7 +2583,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz", "integrity": "sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", @@ -2601,7 +2601,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz", "integrity": "sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2617,7 +2617,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz", "integrity": "sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2633,7 +2633,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz", "integrity": "sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2649,7 +2649,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz", "integrity": "sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2665,7 +2665,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz", "integrity": "sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", @@ -2698,7 +2698,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz", "integrity": "sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", @@ -2717,7 +2717,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz", "integrity": "sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-module-transforms": "^7.25.9", @@ -2734,7 +2734,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz", "integrity": "sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -2751,7 +2751,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz", "integrity": "sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2782,7 +2782,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz", "integrity": "sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2798,7 +2798,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz", "integrity": "sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-compilation-targets": "^7.25.9", @@ -2816,7 +2816,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz", "integrity": "sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -2833,7 +2833,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz", "integrity": "sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2865,7 +2865,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz", "integrity": "sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -2897,7 +2897,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz", "integrity": "sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.25.9", @@ -2915,7 +2915,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz", "integrity": "sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3000,7 +3000,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz", "integrity": "sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -3017,7 +3017,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz", "integrity": "sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -3034,7 +3034,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz", "integrity": "sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3050,7 +3050,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz", "integrity": "sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3066,7 +3066,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz", "integrity": "sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9", @@ -3083,7 +3083,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz", "integrity": "sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3099,7 +3099,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz", "integrity": "sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3115,7 +3115,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.25.9.tgz", "integrity": "sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3150,7 +3150,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz", "integrity": "sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.25.9" @@ -3166,7 +3166,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz", "integrity": "sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -3183,7 +3183,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz", "integrity": "sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -3200,7 +3200,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz", "integrity": "sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.25.9", @@ -3217,7 +3217,7 @@ "version": "7.26.0", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.26.0.tgz", "integrity": "sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.26.0", @@ -3318,7 +3318,7 @@ "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", @@ -5949,22 +5949,6 @@ "node": ">=18" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/@module-federation/runtime": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/@module-federation/runtime/-/runtime-0.5.1.tgz", @@ -9563,7 +9547,6 @@ "version": "10.4.1", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", @@ -9583,7 +9566,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -9596,7 +9578,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1", @@ -9611,7 +9592,6 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, "node_modules/@testing-library/jest-dom": { @@ -9722,7 +9702,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -10015,7 +9994,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -10038,7 +10016,6 @@ "version": "12.2.3", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "*", @@ -10049,7 +10026,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/ms": { @@ -11424,7 +11400,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" @@ -11869,7 +11844,7 @@ "version": "0.4.12", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz", "integrity": "sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.22.6", @@ -11884,7 +11859,7 @@ "version": "0.10.6", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.2", @@ -11898,7 +11873,7 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz", "integrity": "sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.3" @@ -13788,7 +13763,7 @@ "version": "3.39.0", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.39.0.tgz", "integrity": "sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "browserslist": "^4.24.2" @@ -14937,7 +14912,6 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, "license": "MIT" }, "node_modules/dom-converter": { @@ -16010,7 +15984,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" @@ -16251,27 +16225,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/body-parser": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", - "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.0", - "http-errors": "^2.0.0", - "iconv-lite": "^0.6.3", - "on-finished": "^2.4.1", - "qs": "^6.14.0", - "raw-body": "^3.0.0", - "type-is": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/express/node_modules/content-disposition": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", @@ -16404,22 +16357,6 @@ "node": ">= 0.6" } }, - "node_modules/express/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/express/node_modules/send": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", @@ -18923,7 +18860,7 @@ "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -21380,7 +21317,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -22995,7 +22931,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -25522,14 +25458,14 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2" @@ -25548,7 +25484,7 @@ "version": "0.15.2", "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.8.4" @@ -25558,7 +25494,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", @@ -25576,14 +25512,14 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/regjsparser": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", - "dev": true, + "devOptional": true, "license": "BSD-2-Clause", "dependencies": { "jsesc": "~3.0.2" @@ -25596,7 +25532,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, + "devOptional": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -25792,7 +25728,7 @@ "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.0", @@ -28110,7 +28046,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -29297,7 +29233,7 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -29358,7 +29294,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -29368,7 +29304,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", @@ -29382,7 +29318,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -29392,7 +29328,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=4" @@ -33196,16 +33132,6 @@ "@babel/core": "^7.0.0" } }, - "packages/bruno-common/node_modules/@babel/helper-plugin-utils": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz", - "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "packages/bruno-common/node_modules/@babel/helper-replace-supers": { "version": "7.26.5", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz", @@ -33677,10 +33603,12 @@ "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.4", + "@opencollection/types": "0.3.0", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-typescript": "^9.0.2", + "@usebruno/schema-types": "0.0.1", "@web/rollup-plugin-copy": "^0.5.1", "babel-jest": "^29.7.0", "rimraf": "^5.0.7", @@ -35547,17 +35475,6 @@ "rollup-plugin-terser": "^7.0.2" } }, - "packages/bruno-js/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-js/node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", @@ -35619,18 +35536,6 @@ "node": ">=10" } }, - "packages/bruno-js/node_modules/xml-formatter": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/xml-formatter/-/xml-formatter-3.5.0.tgz", - "integrity": "sha512-9ij/f2PLIPv+YDywtdztq7U82kYMDa5yPYwpn0TnXnqJRH6Su8RC/oaw91erHe3aSEbfgBaA1hDzReDFb1SVXw==", - "license": "MIT", - "dependencies": { - "xml-parser-xo": "^4.1.0" - }, - "engines": { - "node": ">= 14" - } - }, "packages/bruno-lang": { "name": "@usebruno/lang", "version": "0.12.0", diff --git a/packages/bruno-app/src/components/ShareCollection/StyledWrapper.js b/packages/bruno-app/src/components/ShareCollection/StyledWrapper.js index 07f04445c..041efffa4 100644 --- a/packages/bruno-app/src/components/ShareCollection/StyledWrapper.js +++ b/packages/bruno-app/src/components/ShareCollection/StyledWrapper.js @@ -26,6 +26,21 @@ const StyledWrapper = styled.div` } } } + + .share-button { + display: flex; + border-radius: ${(props) => props.theme.border.radius.base}; + padding: 10px; + border: 1px solid ${(props) => props.theme.sidebar.collection.item.indentBorder}; + background-color: ${(props) => props.theme.sidebar.bg}; + color: ${(props) => props.theme.text}; + cursor: pointer; + transition: all 0.1s ease; + + &:hover { + background-color: ${(props) => props.theme.listItem.hoverBg}; + } + } `; export default StyledWrapper; diff --git a/packages/bruno-app/src/components/ShareCollection/index.js b/packages/bruno-app/src/components/ShareCollection/index.js index d1e8e11b9..74e2c6d23 100644 --- a/packages/bruno-app/src/components/ShareCollection/index.js +++ b/packages/bruno-app/src/components/ShareCollection/index.js @@ -1,10 +1,11 @@ import React, { useMemo } from 'react'; import Modal from 'components/Modal'; -import { IconUpload, IconLoader2, IconAlertTriangle } from '@tabler/icons'; +import { IconUpload, IconLoader2, IconAlertTriangle, IconFileExport } from '@tabler/icons'; import StyledWrapper from './StyledWrapper'; import Bruno from 'components/Bruno'; import exportBrunoCollection from 'utils/collections/export'; import exportPostmanCollection from 'utils/exporters/postman-collection'; +import exportOpenCollection from 'utils/exporters/opencollection'; import { cloneDeep } from 'lodash'; import { transformCollectionToSaveToExportAsFile } from 'utils/collections/index'; import { useSelector } from 'react-redux'; @@ -48,6 +49,12 @@ const ShareCollection = ({ onClose, collectionUid }) => { onClose(); }; + const handleExportOpenCollection = () => { + const collectionCopy = cloneDeep(collection); + exportOpenCollection(transformCollectionToSaveToExportAsFile(collectionCopy)); + onClose(); + }; + return ( {
@@ -77,10 +84,31 @@ const ShareCollection = ({ onClose, collectionUid }) => {
+
+ {isCollectionLoading ? ( + + ) : ( + + )} +
+
+
OpenCollection
+
{isCollectionLoading ? 'Loading collection...' : 'Export in OpenCollection format'}
+
+
+ +
diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js index e5b90d7d2..c98131bfb 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollection/index.js @@ -8,6 +8,7 @@ import { isInsomniaCollection } from 'utils/importers/insomnia-collection'; import { isOpenApiSpec } from 'utils/importers/openapi-collection'; import { isWSDLCollection } from 'utils/importers/wsdl-collection'; import { isBrunoCollection } from 'utils/importers/bruno-collection'; +import { isOpenCollection } from 'utils/importers/opencollection'; import FullscreenLoader from './FullscreenLoader/index'; const convertFileToObject = async (file) => { @@ -72,6 +73,8 @@ const ImportCollection = ({ onClose, handleSubmit }) => { type = 'postman'; } else if (isInsomniaCollection(data)) { type = 'insomnia'; + } else if (isOpenCollection(data)) { + type = 'opencollection'; } else if (isBrunoCollection(data)) { type = 'bruno'; } else { @@ -159,7 +162,7 @@ const ImportCollection = ({ onClose, handleSubmit }) => {

- Supports Bruno, Postman, Insomnia, OpenAPI v3, and WSDL formats + Supports Bruno, OpenCollection, Postman, Insomnia, OpenAPI v3, and WSDL formats

diff --git a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js index 3d0501aad..967c8edce 100644 --- a/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js +++ b/packages/bruno-app/src/components/Sidebar/ImportCollectionLocation/index.js @@ -9,6 +9,7 @@ import { postmanToBruno } from 'utils/importers/postman-collection'; import { convertInsomniaToBruno } from 'utils/importers/insomnia-collection'; import { convertOpenapiToBruno } from 'utils/importers/openapi-collection'; import { processBrunoCollection } from 'utils/importers/bruno-collection'; +import { processOpenCollection } from 'utils/importers/opencollection'; import { wsdlToBruno } from '@usebruno/converters'; import { toastError } from 'utils/common/error'; import Modal from 'components/Modal'; @@ -37,6 +38,8 @@ const getCollectionName = (format, rawData) => { return rawData.name || 'Insomnia Collection'; case 'bruno': return rawData.name || 'Bruno Collection'; + case 'opencollection': + return rawData.info?.name || 'OpenCollection'; case 'wsdl': return 'WSDL Collection'; default: @@ -65,6 +68,9 @@ const convertCollection = async (format, rawData, groupingType) => { case 'bruno': collection = await processBrunoCollection(rawData); break; + case 'opencollection': + collection = await processOpenCollection(rawData); + break; default: throw new Error('Unknown collection format'); } 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 9a74a08da..189320274 100644 --- a/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js +++ b/packages/bruno-app/src/providers/ReduxStore/slices/workspaces/actions.js @@ -36,6 +36,10 @@ const transformCollection = async (collection, type) => { const { convertOpenapiToBruno } = await import('utils/importers/openapi-collection'); return convertOpenapiToBruno(collection); } + case 'opencollection': { + const { processOpenCollection } = await import('utils/importers/opencollection'); + return processOpenCollection(collection); + } case 'wsdl': { const { wsdlToBruno } = await import('@usebruno/converters'); return wsdlToBruno(collection); diff --git a/packages/bruno-app/src/utils/exporters/opencollection.js b/packages/bruno-app/src/utils/exporters/opencollection.js new file mode 100644 index 000000000..d3fcc5b55 --- /dev/null +++ b/packages/bruno-app/src/utils/exporters/opencollection.js @@ -0,0 +1,23 @@ +import * as FileSaver from 'file-saver'; +import jsyaml from 'js-yaml'; +import { brunoToOpenCollection } from '@usebruno/converters'; +import { sanitizeName } from 'utils/common/regex'; + +export const exportCollection = (collection) => { + const openCollection = brunoToOpenCollection(collection); + + const yamlContent = jsyaml.dump(openCollection, { + indent: 2, + lineWidth: -1, + noRefs: true, + sortKeys: false + }); + + const sanitizedName = sanitizeName(collection.name); + const fileName = `${sanitizedName}.yml`; + const fileBlob = new Blob([yamlContent], { type: 'application/x-yaml' }); + + FileSaver.saveAs(fileBlob, fileName); +}; + +export default exportCollection; diff --git a/packages/bruno-app/src/utils/importers/opencollection.js b/packages/bruno-app/src/utils/importers/opencollection.js new file mode 100644 index 000000000..1880e583b --- /dev/null +++ b/packages/bruno-app/src/utils/importers/opencollection.js @@ -0,0 +1,81 @@ +import each from 'lodash/each'; +import { uuid } from 'utils/common'; +import { BrunoError } from 'utils/common/error'; +import { validateSchema, updateUidsInCollection, hydrateSeqInCollection } from './common'; +import { openCollectionToBruno } from '@usebruno/converters'; + +const addUidsToRoot = (collection) => { + if (collection.root?.request?.headers) { + each(collection.root.request.headers, (header) => { + header.uid = uuid(); + }); + } + if (collection.root?.request?.vars?.req) { + each(collection.root.request.vars.req, (v) => { + v.uid = uuid(); + }); + } + if (collection.root?.request?.vars?.res) { + each(collection.root.request.vars.res, (v) => { + v.uid = uuid(); + }); + } + + const addUidsToFolderRoot = (items) => { + each(items, (item) => { + if (item.type === 'folder') { + if (item.root?.request?.headers) { + each(item.root.request.headers, (header) => { + header.uid = uuid(); + }); + } + if (item.root?.request?.vars?.req) { + each(item.root.request.vars.req, (v) => { + v.uid = uuid(); + }); + } + if (item.root?.request?.vars?.res) { + each(item.root.request.vars.res, (v) => { + v.uid = uuid(); + }); + } + if (item.items?.length) { + addUidsToFolderRoot(item.items); + } + } + }); + }; + + addUidsToFolderRoot(collection.items); + return collection; +}; + +export const processOpenCollection = async (jsonData) => { + try { + let collection = openCollectionToBruno(jsonData); + collection = hydrateSeqInCollection(collection); + collection = updateUidsInCollection(collection); + collection = addUidsToRoot(collection); + await validateSchema(collection); + return collection; + } catch (err) { + console.error('Error processing OpenCollection:', err); + throw new BrunoError('Import OpenCollection failed'); + } +}; + +export const isOpenCollection = (data) => { + if (typeof data !== 'object' || data === null) { + return false; + } + + if (typeof data.opencollection !== 'string' || !data.opencollection.trim()) { + return false; + } + + if (typeof data.info !== 'object' || data.info === null) { + return false; + } + + return true; +}; diff --git a/packages/bruno-converters/package.json b/packages/bruno-converters/package.json index 4e780e56e..1331fdca9 100644 --- a/packages/bruno-converters/package.json +++ b/packages/bruno-converters/package.json @@ -4,7 +4,6 @@ "license": "MIT", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", - "types": "dist/index.d.ts", "files": [ "dist", "src", @@ -29,6 +28,8 @@ "devDependencies": { "@babel/core": "^7.25.2", "@babel/preset-env": "^7.25.4", + "@opencollection/types": "0.3.0", + "@usebruno/schema-types": "0.0.1", "@rollup/plugin-alias": "^5.1.0", "@rollup/plugin-commonjs": "^23.0.2", "@rollup/plugin-node-resolve": "^15.0.1", diff --git a/packages/bruno-converters/rollup.config.js b/packages/bruno-converters/rollup.config.js index ec9a7a4c9..e26a50014 100644 --- a/packages/bruno-converters/rollup.config.js +++ b/packages/bruno-converters/rollup.config.js @@ -1,43 +1,89 @@ const { nodeResolve } = require('@rollup/plugin-node-resolve'); const commonjs = require('@rollup/plugin-commonjs'); +const typescript = require('@rollup/plugin-typescript'); const { terser } = require('rollup-plugin-terser'); const peerDepsExternal = require('rollup-plugin-peer-deps-external'); const { copy } = require('@web/rollup-plugin-copy'); +const path = require('path'); const packageJson = require('./package.json'); -const alias = require('@rollup/plugin-alias'); -const path = require('path'); + +const externalDeps = [ + '@usebruno/schema', + '@usebruno/schema-types', + /@usebruno\/schema-types\/.*/, + '@opencollection/types', + /@opencollection\/types\/.*/, + // Runtime dependencies + 'lodash', + 'lodash/each', + 'lodash/get', + 'lodash/cloneDeep', + 'lodash/map', + 'js-yaml', + 'jscodeshift', + 'nanoid', + 'xml2js', + // Node built-ins + 'path', + 'fs' +]; module.exports = [ { input: 'src/index.js', - output: [ - { - dir: path.dirname(packageJson.main), - format: 'cjs', - sourcemap: true - }, - { - dir: path.dirname(packageJson.module), - format: 'esm', - sourcemap: true - } - ], + output: { + dir: path.dirname(packageJson.main), + format: 'cjs', + sourcemap: true, + exports: 'named', + entryFileNames: 'index.js' + }, plugins: [ peerDepsExternal(), nodeResolve({ - preferBuiltins: true, - extensions: ['.js', '.css'] // Resolve .js files + extensions: ['.js', '.ts', '.tsx', '.json'] }), commonjs(), - terser(), - alias({ - entries: [{ find: 'src', replacement: path.resolve(__dirname, 'src') }] + typescript({ + tsconfig: './tsconfig.json', + sourceMap: true, + outDir: path.dirname(packageJson.main) }), + terser(), copy({ patterns: 'src/workers/scripts/**/*', rootDir: '.' }) - ] + ], + external: externalDeps + }, + { + input: 'src/index.js', + output: { + dir: path.dirname(packageJson.module), + format: 'esm', + sourcemap: true, + exports: 'named', + entryFileNames: 'index.js' + }, + plugins: [ + peerDepsExternal(), + nodeResolve({ + extensions: ['.js', '.ts', '.tsx', '.json'] + }), + commonjs(), + typescript({ + tsconfig: './tsconfig.json', + sourceMap: true, + outDir: path.dirname(packageJson.module) + }), + terser(), + copy({ + patterns: 'src/workers/scripts/**/*', + rootDir: '.' + }) + ], + external: externalDeps } ]; diff --git a/packages/bruno-converters/src/index.js b/packages/bruno-converters/src/index.js index ba2215ea5..017c742a3 100644 --- a/packages/bruno-converters/src/index.js +++ b/packages/bruno-converters/src/index.js @@ -5,3 +5,5 @@ export { default as openApiToBruno } from './openapi/openapi-to-bruno.js'; export { default as insomniaToBruno } from './insomnia/insomnia-to-bruno.js'; export { default as wsdlToBruno } from './wsdl/wsdl-to-bruno.js'; export { default as postmanTranslation } from './postman/postman-translations.js'; +export { openCollectionToBruno } from './opencollection/opencollection-to-bruno.js'; +export { brunoToOpenCollection } from './opencollection/bruno-to-opencollection.js'; diff --git a/packages/bruno-converters/src/opencollection/bruno-to-opencollection.ts b/packages/bruno-converters/src/opencollection/bruno-to-opencollection.ts new file mode 100644 index 000000000..64d168a16 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/bruno-to-opencollection.ts @@ -0,0 +1,172 @@ +import { toOpenCollectionAuth, toOpenCollectionHeaders, toOpenCollectionScripts, toOpenCollectionVariables } from "./common"; +import { toOpenCollectionEnvironments } from "./environment"; +import { toOpenCollectionFolder } from "./folder"; +import { toOpenCollectionItems } from "./items"; +import { BrunoCollection, BrunoCollectionRoot, BrunoConfig, ClientCertificate, CollectionConfig, OpenCollection, PemCertificate, Pkcs12Certificate, Protobuf } from "./types"; + +const toOpenCollectionConfig = (brunoConfig: BrunoConfig | undefined): CollectionConfig | undefined => { + if (!brunoConfig) { + return undefined; + } + + const config: CollectionConfig = {}; + + if (brunoConfig.protobuf?.protoFiles?.length || brunoConfig.protobuf?.importPaths?.length) { + config.protobuf = {} as Protobuf; + + if (brunoConfig.protobuf.protoFiles?.length) { + config.protobuf.protoFiles = brunoConfig.protobuf.protoFiles.map((f) => ({ + type: 'file' as const, + path: f.path + })); + } + + if (brunoConfig.protobuf.importPaths?.length) { + config.protobuf.importPaths = brunoConfig.protobuf.importPaths.map((p) => { + const importPath: { path: string; disabled?: boolean } = { path: p.path }; + if (p.disabled) { + importPath.disabled = true; + } + return importPath; + }); + } + } + + if (brunoConfig.proxy?.enabled) { + if (brunoConfig.proxy.enabled === 'global') { + config.proxy = 'inherit'; + } else { + config.proxy = { + protocol: brunoConfig.proxy.protocol || 'http', + hostname: brunoConfig.proxy.hostname || '', + port: brunoConfig.proxy.port || 0 + }; + + if (brunoConfig.proxy.auth?.enabled) { + config.proxy.auth = { + username: brunoConfig.proxy.auth.username || '', + password: brunoConfig.proxy.auth.password || '' + }; + } + + if (brunoConfig.proxy.bypassProxy) { + config.proxy.bypassProxy = brunoConfig.proxy.bypassProxy; + } + } + } + + if (brunoConfig.clientCertificates?.certs?.length) { + config.clientCertificates = brunoConfig.clientCertificates.certs + .map((cert): ClientCertificate | null => { + if (cert.type === 'pem') { + const pemCert: PemCertificate = { + domain: cert.domain || '', + type: 'pem', + certificateFilePath: cert.certFilePath || '', + privateKeyFilePath: cert.keyFilePath || '' + }; + if (cert.passphrase) { + pemCert.passphrase = cert.passphrase; + } + return pemCert; + } else if (cert.type === 'pkcs12') { + const pkcs12Cert: Pkcs12Certificate = { + domain: cert.domain || '', + type: 'pkcs12', + pkcs12FilePath: cert.pfxFilePath || '' + }; + if (cert.passphrase) { + pkcs12Cert.passphrase = cert.passphrase; + } + return pkcs12Cert; + } + return null; + }) + .filter((cert): cert is ClientCertificate => cert !== null); + } + + return Object.keys(config).length > 0 ? config : undefined; +}; + +const hasRequestDefaults = (root: BrunoCollectionRoot | undefined): boolean => { + const request = root?.request; + return Boolean( + request?.headers?.length || + request?.vars?.req?.length || + request?.script?.req || + request?.script?.res || + request?.tests || + (request?.auth && request.auth.mode !== 'none') + ); +}; + +export const brunoToOpenCollection = (collection: BrunoCollection): OpenCollection => { + const openCollection: OpenCollection = { + opencollection: '1.0.0', + info: { + name: collection.name || 'Untitled Collection' + } + }; + + const config = toOpenCollectionConfig(collection.brunoConfig as BrunoConfig); + if (config) { + openCollection.config = config; + } + + const environments = toOpenCollectionEnvironments(collection.environments ?? undefined); + if (environments?.length) { + if (!openCollection.config) { + openCollection.config = {}; + } + openCollection.config.environments = environments; + } + + const items = toOpenCollectionItems(collection.items, toOpenCollectionFolder); + if (items.length) { + openCollection.items = items as OpenCollection['items']; + } + + if (hasRequestDefaults(collection.root as BrunoCollectionRoot)) { + const request = (collection.root as BrunoCollectionRoot)?.request; + openCollection.request = {}; + + const headers = toOpenCollectionHeaders(request?.headers); + if (headers) { + openCollection.request.headers = headers; + } + + const auth = toOpenCollectionAuth(request?.auth); + if (auth) { + openCollection.request.auth = auth; + } + + const variables = toOpenCollectionVariables(request?.vars); + if (variables) { + openCollection.request.variables = variables; + } + + const scripts = toOpenCollectionScripts(request as any); + if (scripts) { + openCollection.request.scripts = scripts; + } + } + + if ((collection.root as BrunoCollectionRoot)?.docs) { + openCollection.docs = { + content: (collection.root as BrunoCollectionRoot).docs!, + type: 'text/markdown' + }; + } + + openCollection.bundled = true; + + const extensions: { ignore?: string[] } = {}; + if ((collection.brunoConfig as BrunoConfig)?.ignore?.length) { + extensions.ignore = (collection.brunoConfig as BrunoConfig).ignore; + } + if (Object.keys(extensions).length > 0) { + openCollection.extensions = extensions; + } + + return openCollection; +}; \ No newline at end of file diff --git a/packages/bruno-converters/src/opencollection/common/actions.ts b/packages/bruno-converters/src/opencollection/common/actions.ts new file mode 100644 index 000000000..f8d6de5a6 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/actions.ts @@ -0,0 +1,83 @@ +import { uuid } from '../../common/index.js'; +import type { + Action, + ActionSetVariable, + ActionVariableScope, + BrunoVariable, + BrunoVariables +} from '../types'; + +/** + * Convert Bruno post-response variables to OpenCollection actions. + * Post-response variables in Bruno are converted to 'set-variable' actions + * with phase 'after-response'. + */ +export const toOpenCollectionActions = (resVariables: BrunoVariables | null | undefined): Action[] | undefined => { + if (!resVariables?.length) { + return undefined; + } + + const actions: Action[] = resVariables.map((v: BrunoVariable): ActionSetVariable => { + const action: ActionSetVariable = { + type: 'set-variable', + phase: 'after-response', + selector: { + expression: v.value || '', + method: 'jsonq' + }, + variable: { + name: v.name || '', + scope: v.local ? 'request' : 'runtime' as ActionVariableScope + } + }; + + if (v.description && typeof v.description === 'string' && v.description.trim().length) { + action.description = v.description; + } + + if (v.enabled === false) { + action.disabled = true; + } + + return action; + }); + + return actions.length > 0 ? actions : undefined; +}; + +/** + * Convert OpenCollection actions to Bruno post-response variables. + * Only 'set-variable' actions with phase 'after-response' are converted. + */ +export const fromOpenCollectionActions = (actions: Action[] | null | undefined): BrunoVariables => { + if (!actions?.length) { + return []; + } + + const resVars: BrunoVariables = []; + + actions.forEach((action: Action) => { + // Only process 'set-variable' actions with 'after-response' phase + if (action.type === 'set-variable' && action.phase === 'after-response') { + const setVarAction = action as ActionSetVariable; + + const variable: BrunoVariable = { + uid: uuid(), + name: setVarAction.variable?.name || '', + value: setVarAction.selector?.expression || '', + enabled: setVarAction.disabled !== true, + local: setVarAction.variable?.scope === 'request' + }; + + if (setVarAction.description) { + variable.description = typeof setVarAction.description === 'string' + ? setVarAction.description + : (setVarAction.description as { content?: string })?.content || ''; + } + + resVars.push(variable); + } + }); + + return resVars; +}; diff --git a/packages/bruno-converters/src/opencollection/common/assertions.ts b/packages/bruno-converters/src/opencollection/common/assertions.ts new file mode 100644 index 000000000..a91a882c9 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/assertions.ts @@ -0,0 +1,48 @@ +import { uuid } from '../../common/index.js'; +import type { + Assertion, + BrunoKeyValue +} from '../types'; + +export const fromOpenCollectionAssertions = (assertions: Assertion[] | undefined): BrunoKeyValue[] => { + if (!assertions?.length) { + return []; + } + + return assertions.map((a): BrunoKeyValue => ({ + uid: uuid(), + name: a.expression || '', + value: `${a.operator || 'eq'} ${a.value || ''}`.trim(), + description: typeof a.description === 'string' ? a.description : a.description?.content || null, + enabled: a.disabled !== true + })); +}; + +export const toOpenCollectionAssertions = (assertions: BrunoKeyValue[] | null | undefined): Assertion[] | undefined => { + if (!assertions?.length) { + return undefined; + } + + return assertions.map((a): Assertion => { + const valueStr = a.value || ''; + const parts = valueStr.split(' '); + const operator = parts[0] || 'eq'; + const value = parts.slice(1).join(' '); + + const ocAssertion: Assertion = { + expression: a.name || '', + operator, + value + }; + + if (a.enabled === false) { + ocAssertion.disabled = true; + } + + if (a.description && typeof a.description === 'string' && a.description.trim().length) { + ocAssertion.description = a.description; + } + + return ocAssertion; + }); +}; diff --git a/packages/bruno-converters/src/opencollection/common/auth.ts b/packages/bruno-converters/src/opencollection/common/auth.ts new file mode 100644 index 000000000..9403692dd --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/auth.ts @@ -0,0 +1,470 @@ +import type { + Auth, + AuthBasic, + AuthBearer, + AuthDigest, + AuthNTLM, + AuthAwsV4, + AuthApiKey, + AuthWsse, + AuthOAuth2, + BrunoAuth, + BrunoOAuth2 +} from '../types'; + +const fromOpenCollectionOAuth2 = (auth: AuthOAuth2): BrunoAuth => { + const getTokenPlacement = (tokenConfig: AuthOAuth2['tokenConfig']): string => { + if (tokenConfig?.placement && 'query' in tokenConfig.placement) { + return 'query'; + } + return 'header'; + }; + + const getTokenHeaderPrefix = (tokenConfig: AuthOAuth2['tokenConfig']): string => { + if (tokenConfig?.placement && 'header' in tokenConfig.placement) { + return tokenConfig.placement.header; + } + return 'Bearer'; + }; + + const getTokenQueryKey = (tokenConfig: AuthOAuth2['tokenConfig']): string => { + if (tokenConfig?.placement && 'query' in tokenConfig.placement) { + return tokenConfig.placement.query; + } + return 'access_token'; + }; + + const getCredentialsPlacement = (credentials: AuthOAuth2['credentials']): 'body' | 'basic_auth_header' => { + if (credentials && 'placement' in credentials && credentials.placement === 'basic_auth_header') { + return 'basic_auth_header'; + } + return 'body'; + }; + + const buildOAuth2Config = (base: Partial): BrunoAuth => { + const brunoAuth: BrunoAuth = { + mode: 'oauth2', + awsv4: null, + basic: null, + bearer: null, + digest: null, + ntlm: null, + oauth2: { + grantType: base.grantType || 'client_credentials', + username: base.username || null, + password: base.password || null, + callbackUrl: base.callbackUrl || null, + authorizationUrl: base.authorizationUrl || null, + accessTokenUrl: base.accessTokenUrl || null, + clientId: base.clientId || null, + clientSecret: base.clientSecret || null, + scope: base.scope || null, + state: base.state || null, + pkce: base.pkce || null, + credentialsPlacement: base.credentialsPlacement || null, + credentialsId: base.credentialsId || null, + tokenPlacement: base.tokenPlacement || null, + tokenHeaderPrefix: base.tokenHeaderPrefix || null, + tokenQueryKey: base.tokenQueryKey || null, + refreshTokenUrl: base.refreshTokenUrl || null, + autoRefreshToken: base.autoRefreshToken || null, + autoFetchToken: base.autoFetchToken || null, + additionalParameters: null + }, + wsse: null, + apikey: null + }; + return brunoAuth; + }; + + switch (auth.flow) { + case 'client_credentials': + return buildOAuth2Config({ + grantType: 'client_credentials', + accessTokenUrl: auth.accessTokenUrl || null, + refreshTokenUrl: auth.refreshTokenUrl || null, + clientId: auth.credentials?.clientId || null, + clientSecret: auth.credentials?.clientSecret || null, + scope: auth.scope || null, + credentialsPlacement: getCredentialsPlacement(auth.credentials), + credentialsId: auth.tokenConfig?.id || 'credentials', + tokenPlacement: getTokenPlacement(auth.tokenConfig), + tokenHeaderPrefix: getTokenHeaderPrefix(auth.tokenConfig), + tokenQueryKey: getTokenQueryKey(auth.tokenConfig), + autoFetchToken: auth.settings?.autoFetchToken !== false, + autoRefreshToken: auth.settings?.autoRefreshToken !== false + }); + + case 'resource_owner_password_credentials': + return buildOAuth2Config({ + grantType: 'password', + accessTokenUrl: auth.accessTokenUrl || null, + refreshTokenUrl: auth.refreshTokenUrl || null, + clientId: auth.credentials?.clientId || null, + clientSecret: auth.credentials?.clientSecret || null, + username: auth.resourceOwner?.username || null, + password: auth.resourceOwner?.password || null, + scope: auth.scope || null, + credentialsPlacement: getCredentialsPlacement(auth.credentials), + credentialsId: auth.tokenConfig?.id || 'credentials', + tokenPlacement: getTokenPlacement(auth.tokenConfig), + tokenHeaderPrefix: getTokenHeaderPrefix(auth.tokenConfig), + tokenQueryKey: getTokenQueryKey(auth.tokenConfig), + autoFetchToken: auth.settings?.autoFetchToken !== false, + autoRefreshToken: auth.settings?.autoRefreshToken !== false + }); + + case 'authorization_code': + return buildOAuth2Config({ + grantType: 'authorization_code', + authorizationUrl: auth.authorizationUrl || null, + accessTokenUrl: auth.accessTokenUrl || null, + refreshTokenUrl: auth.refreshTokenUrl || null, + callbackUrl: auth.callbackUrl || null, + clientId: auth.credentials?.clientId || null, + clientSecret: auth.credentials?.clientSecret || null, + scope: auth.scope || null, + pkce: auth.pkce?.enabled || null, + credentialsPlacement: getCredentialsPlacement(auth.credentials), + credentialsId: auth.tokenConfig?.id || 'credentials', + tokenPlacement: getTokenPlacement(auth.tokenConfig), + tokenHeaderPrefix: getTokenHeaderPrefix(auth.tokenConfig), + tokenQueryKey: getTokenQueryKey(auth.tokenConfig), + autoFetchToken: auth.settings?.autoFetchToken !== false, + autoRefreshToken: auth.settings?.autoRefreshToken !== false + }); + + case 'implicit': + return buildOAuth2Config({ + grantType: 'implicit', + authorizationUrl: auth.authorizationUrl || null, + callbackUrl: auth.callbackUrl || null, + clientId: auth.credentials?.clientId || null, + scope: auth.scope || null, + state: auth.state || null, + credentialsId: auth.tokenConfig?.id || 'credentials', + tokenPlacement: getTokenPlacement(auth.tokenConfig), + tokenHeaderPrefix: getTokenHeaderPrefix(auth.tokenConfig), + tokenQueryKey: getTokenQueryKey(auth.tokenConfig), + autoFetchToken: auth.settings?.autoFetchToken !== false + }); + + default: + return { + mode: 'none', + awsv4: null, + basic: null, + bearer: null, + digest: null, + ntlm: null, + oauth2: null, + wsse: null, + apikey: null + }; + } +}; + +export const fromOpenCollectionAuth = (auth: Auth | undefined): BrunoAuth => { + const defaultAuth: BrunoAuth = { + mode: 'none', + awsv4: null, + basic: null, + bearer: null, + digest: null, + ntlm: null, + oauth2: null, + wsse: null, + apikey: null + }; + + if (!auth) { + return defaultAuth; + } + + if (auth === 'inherit') { + return { ...defaultAuth, mode: 'inherit' }; + } + + switch (auth.type) { + case 'basic': { + const basicAuth = auth as AuthBasic; + return { + ...defaultAuth, + mode: 'basic', + basic: { + username: basicAuth.username || null, + password: basicAuth.password || null + } + }; + } + + case 'bearer': { + const bearerAuth = auth as AuthBearer; + return { + ...defaultAuth, + mode: 'bearer', + bearer: { + token: bearerAuth.token || null + } + }; + } + + case 'digest': { + const digestAuth = auth as AuthDigest; + return { + ...defaultAuth, + mode: 'digest', + digest: { + username: digestAuth.username || null, + password: digestAuth.password || null + } + }; + } + + case 'ntlm': { + const ntlmAuth = auth as AuthNTLM; + return { + ...defaultAuth, + mode: 'ntlm', + ntlm: { + username: ntlmAuth.username || null, + password: ntlmAuth.password || null, + domain: ntlmAuth.domain || null + } + }; + } + + case 'awsv4': { + const awsAuth = auth as AuthAwsV4; + return { + ...defaultAuth, + mode: 'awsv4', + awsv4: { + accessKeyId: awsAuth.accessKeyId || null, + secretAccessKey: awsAuth.secretAccessKey || null, + sessionToken: awsAuth.sessionToken || null, + service: awsAuth.service || null, + region: awsAuth.region || null, + profileName: awsAuth.profileName || null + } + }; + } + + case 'apikey': { + const apiKeyAuth = auth as AuthApiKey; + return { + ...defaultAuth, + mode: 'apikey', + apikey: { + key: apiKeyAuth.key || null, + value: apiKeyAuth.value || null, + placement: apiKeyAuth.placement === 'query' ? 'queryparams' : (apiKeyAuth.placement === 'header' ? 'header' : null) + } + }; + } + + case 'wsse': { + const wsseAuth = auth as AuthWsse; + return { + ...defaultAuth, + mode: 'wsse', + wsse: { + username: wsseAuth.username || null, + password: wsseAuth.password || null + } + }; + } + + case 'oauth2': + return fromOpenCollectionOAuth2(auth as AuthOAuth2); + + default: + return defaultAuth; + } +}; + +const toOpenCollectionOAuth2 = (oauth2: BrunoOAuth2 | null | undefined): AuthOAuth2 | undefined => { + if (!oauth2) { + return undefined; + } + + const base = { type: 'oauth2' as const }; + + switch (oauth2.grantType) { + case 'client_credentials': + return { + ...base, + flow: 'client_credentials', + accessTokenUrl: oauth2.accessTokenUrl || '', + refreshTokenUrl: oauth2.refreshTokenUrl || '', + credentials: { + clientId: oauth2.clientId || '', + clientSecret: oauth2.clientSecret || '', + placement: oauth2.credentialsPlacement === 'basic_auth_header' ? 'basic_auth_header' : 'body' + }, + scope: oauth2.scope || '', + tokenConfig: { + id: oauth2.credentialsId || 'credentials', + placement: oauth2.tokenPlacement === 'query' + ? { query: oauth2.tokenQueryKey || 'access_token' } + : { header: oauth2.tokenHeaderPrefix || 'Bearer' } + }, + settings: { + autoFetchToken: oauth2.autoFetchToken !== false, + autoRefreshToken: oauth2.autoRefreshToken !== false + } + }; + + case 'password': + return { + ...base, + flow: 'resource_owner_password_credentials', + accessTokenUrl: oauth2.accessTokenUrl || '', + refreshTokenUrl: oauth2.refreshTokenUrl || '', + credentials: { + clientId: oauth2.clientId || '', + clientSecret: oauth2.clientSecret || '', + placement: oauth2.credentialsPlacement === 'basic_auth_header' ? 'basic_auth_header' : 'body' + }, + resourceOwner: { + username: oauth2.username || '', + password: oauth2.password || '' + }, + scope: oauth2.scope || '', + tokenConfig: { + id: oauth2.credentialsId || 'credentials', + placement: oauth2.tokenPlacement === 'query' + ? { query: oauth2.tokenQueryKey || 'access_token' } + : { header: oauth2.tokenHeaderPrefix || 'Bearer' } + }, + settings: { + autoFetchToken: oauth2.autoFetchToken !== false, + autoRefreshToken: oauth2.autoRefreshToken !== false + } + }; + + case 'authorization_code': + return { + ...base, + flow: 'authorization_code', + authorizationUrl: oauth2.authorizationUrl || '', + accessTokenUrl: oauth2.accessTokenUrl || '', + refreshTokenUrl: oauth2.refreshTokenUrl || '', + callbackUrl: oauth2.callbackUrl || '', + credentials: { + clientId: oauth2.clientId || '', + clientSecret: oauth2.clientSecret || '', + placement: oauth2.credentialsPlacement === 'basic_auth_header' ? 'basic_auth_header' : 'body' + }, + scope: oauth2.scope || '', + pkce: oauth2.pkce ? { enabled: true, method: 'S256' } : undefined, + tokenConfig: { + id: oauth2.credentialsId || 'credentials', + placement: oauth2.tokenPlacement === 'query' + ? { query: oauth2.tokenQueryKey || 'access_token' } + : { header: oauth2.tokenHeaderPrefix || 'Bearer' } + }, + settings: { + autoFetchToken: oauth2.autoFetchToken !== false, + autoRefreshToken: oauth2.autoRefreshToken !== false + } + }; + + case 'implicit': + return { + ...base, + flow: 'implicit', + authorizationUrl: oauth2.authorizationUrl || '', + callbackUrl: oauth2.callbackUrl || '', + credentials: { + clientId: oauth2.clientId || '' + }, + scope: oauth2.scope || '', + state: oauth2.state || '', + tokenConfig: { + id: oauth2.credentialsId || 'credentials', + placement: oauth2.tokenPlacement === 'query' + ? { query: oauth2.tokenQueryKey || 'access_token' } + : { header: oauth2.tokenHeaderPrefix || 'Bearer' } + }, + settings: { + autoFetchToken: oauth2.autoFetchToken !== false + } + }; + + default: + return undefined; + } +}; + +export const toOpenCollectionAuth = (auth: BrunoAuth | null | undefined): Auth | undefined => { + if (!auth || auth.mode === 'none') { + return undefined; + } + + if (auth.mode === 'inherit') { + return 'inherit'; + } + + switch (auth.mode) { + case 'basic': + return { + type: 'basic', + username: auth.basic?.username || '', + password: auth.basic?.password || '' + }; + + case 'bearer': + return { + type: 'bearer', + token: auth.bearer?.token || '' + }; + + case 'digest': + return { + type: 'digest', + username: auth.digest?.username || '', + password: auth.digest?.password || '' + }; + + case 'ntlm': + return { + type: 'ntlm', + username: auth.ntlm?.username || '', + password: auth.ntlm?.password || '', + domain: auth.ntlm?.domain || '' + }; + + case 'awsv4': + return { + type: 'awsv4', + accessKeyId: auth.awsv4?.accessKeyId || '', + secretAccessKey: auth.awsv4?.secretAccessKey || '', + sessionToken: auth.awsv4?.sessionToken || '', + service: auth.awsv4?.service || '', + region: auth.awsv4?.region || '', + profileName: auth.awsv4?.profileName || '' + }; + + case 'apikey': + return { + type: 'apikey', + key: auth.apikey?.key || '', + value: auth.apikey?.value || '', + placement: auth.apikey?.placement === 'queryparams' ? 'query' : 'header' + }; + + case 'wsse': + return { + type: 'wsse', + username: auth.wsse?.username || '', + password: auth.wsse?.password || '' + }; + + case 'oauth2': + return toOpenCollectionOAuth2(auth.oauth2); + + default: + return undefined; + } +}; diff --git a/packages/bruno-converters/src/opencollection/common/body.ts b/packages/bruno-converters/src/opencollection/common/body.ts new file mode 100644 index 000000000..2b473530e --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/body.ts @@ -0,0 +1,227 @@ +import { uuid } from '../../common/index.js'; +import type { + HttpRequestBody, + RawBody, + FormUrlEncodedBody, + FormUrlEncodedEntry, + MultipartFormBody, + MultipartFormEntry, + FileBody, + FileBodyVariant, + GraphQLBody, + BrunoHttpRequestBody, + BrunoKeyValue, + BrunoMultipartFormEntry, + BrunoFileEntry, + BrunoGraphqlBody +} from '../types'; + +export const fromOpenCollectionBody = (body: HttpRequestBody | GraphQLBody | undefined, requestType: string = 'http'): BrunoHttpRequestBody => { + const defaultBody: BrunoHttpRequestBody = { + mode: 'none', + json: null, + text: null, + xml: null, + sparql: null, + formUrlEncoded: [], + multipartForm: [], + graphql: null, + file: [] + }; + + if (!body) { + return defaultBody; + } + + if (requestType === 'graphql') { + const gqlBody = body as GraphQLBody; + return { + ...defaultBody, + mode: 'graphql', + graphql: { + query: gqlBody.query || '', + variables: gqlBody.variables || '' + } + }; + } + + const httpBody = body as HttpRequestBody; + + if ('type' in httpBody) { + switch (httpBody.type) { + case 'json': { + const rawBody = httpBody as RawBody; + return { + ...defaultBody, + mode: 'json', + json: rawBody.data || '' + }; + } + + case 'text': { + const rawBody = httpBody as RawBody; + return { + ...defaultBody, + mode: 'text', + text: rawBody.data || '' + }; + } + + case 'xml': { + const rawBody = httpBody as RawBody; + return { + ...defaultBody, + mode: 'xml', + xml: rawBody.data || '' + }; + } + + case 'sparql': { + const rawBody = httpBody as RawBody; + return { + ...defaultBody, + mode: 'sparql', + sparql: rawBody.data || '' + }; + } + + case 'form-urlencoded': { + const formBody = httpBody as FormUrlEncodedBody; + return { + ...defaultBody, + mode: 'formUrlEncoded', + formUrlEncoded: (formBody.data || []).map((field): BrunoKeyValue => ({ + uid: uuid(), + name: field.name || '', + value: field.value || '', + description: typeof field.description === 'string' ? field.description : field.description?.content || null, + enabled: field.disabled !== true + })) + }; + } + + case 'multipart-form': { + const multipartBody = httpBody as MultipartFormBody; + return { + ...defaultBody, + mode: 'multipartForm', + multipartForm: (multipartBody.data || []).map((field): BrunoMultipartFormEntry => ({ + uid: uuid(), + type: field.type || 'text', + name: field.name || '', + value: Array.isArray(field.value) ? field.value : (field.value || ''), + description: typeof field.description === 'string' ? field.description : field.description?.content || null, + contentType: null, + enabled: field.disabled !== true + })) + }; + } + + case 'file': { + const fileBody = httpBody as FileBody; + return { + ...defaultBody, + mode: 'file', + file: (fileBody.data || []).map((file): BrunoFileEntry => ({ + uid: uuid(), + filePath: file.filePath || '', + contentType: file.contentType || '', + selected: file.selected !== false + })) + }; + } + } + } + + return defaultBody; +}; + +export const toOpenCollectionBody = (body: BrunoHttpRequestBody | null | undefined): HttpRequestBody | undefined => { + if (!body || body.mode === 'none') { + return undefined; + } + + switch (body.mode) { + case 'json': + return { type: 'json', data: body.json || '' }; + + case 'text': + return { type: 'text', data: body.text || '' }; + + case 'xml': + return { type: 'xml', data: body.xml || '' }; + + case 'sparql': + return { type: 'sparql', data: body.sparql || '' }; + + case 'formUrlEncoded': { + const formData: FormUrlEncodedEntry[] = (body.formUrlEncoded || []).map((field): FormUrlEncodedEntry => { + const entry: FormUrlEncodedEntry = { + name: field.name || '', + value: field.value || '' + }; + + if (field.description && typeof field.description === 'string' && field.description.trim().length) { + entry.description = field.description; + } + + if (field.enabled === false) { + entry.disabled = true; + } + + return entry; + }); + + return { type: 'form-urlencoded', data: formData }; + } + + case 'multipartForm': { + const multipartData: MultipartFormEntry[] = (body.multipartForm || []).map((field): MultipartFormEntry => { + const entry: MultipartFormEntry = { + name: field.name || '', + type: field.type || 'text', + value: field.value || '' + }; + + if (field.description && typeof field.description === 'string' && field.description.trim().length) { + entry.description = field.description; + } + + if (field.enabled === false) { + entry.disabled = true; + } + + return entry; + }); + + return { type: 'multipart-form', data: multipartData }; + } + + case 'file': { + const fileData: FileBodyVariant[] = (body.file || []).map((file): FileBodyVariant => ({ + filePath: file.filePath || '', + contentType: file.contentType || '', + selected: file.selected !== false + })); + + return { type: 'file', data: fileData }; + } + + case 'graphql': + return undefined; + + default: + return undefined; + } +}; + +export const toOpenCollectionGraphqlBody = (body: BrunoHttpRequestBody | null | undefined): GraphQLBody | undefined => { + if (!body || body.mode !== 'graphql' || !body.graphql) { + return undefined; + } + + return { + query: body.graphql.query || '', + variables: body.graphql.variables || '' + }; +}; diff --git a/packages/bruno-converters/src/opencollection/common/headers.ts b/packages/bruno-converters/src/opencollection/common/headers.ts new file mode 100644 index 000000000..d845ecc2e --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/headers.ts @@ -0,0 +1,44 @@ +import { uuid } from '../../common/index.js'; +import type { + HttpRequestHeader, + BrunoKeyValue +} from '../types'; + +export const fromOpenCollectionHeaders = (headers: HttpRequestHeader[] | undefined): BrunoKeyValue[] => { + if (!headers?.length) { + return []; + } + + return headers.map((header): BrunoKeyValue => ({ + uid: uuid(), + name: header.name || '', + value: header.value || '', + description: typeof header.description === 'string' ? header.description : header.description?.content || null, + enabled: header.disabled !== true + })); +}; + +export const toOpenCollectionHeaders = (headers: BrunoKeyValue[] | null | undefined): HttpRequestHeader[] | undefined => { + if (!headers?.length) { + return undefined; + } + + const ocHeaders = headers.map((header): HttpRequestHeader => { + const httpHeader: HttpRequestHeader = { + name: header.name || '', + value: header.value || '' + }; + + if (header.description && typeof header.description === 'string' && header.description.trim().length) { + httpHeader.description = header.description; + } + + if (header.enabled === false) { + httpHeader.disabled = true; + } + + return httpHeader; + }); + + return ocHeaders.length ? ocHeaders : undefined; +}; diff --git a/packages/bruno-converters/src/opencollection/common/index.ts b/packages/bruno-converters/src/opencollection/common/index.ts new file mode 100644 index 000000000..3ef2e2e5d --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/index.ts @@ -0,0 +1,8 @@ +export { fromOpenCollectionAuth, toOpenCollectionAuth } from './auth'; +export { fromOpenCollectionHeaders, toOpenCollectionHeaders } from './headers'; +export { fromOpenCollectionParams, toOpenCollectionParams } from './params'; +export { fromOpenCollectionBody, toOpenCollectionBody, toOpenCollectionGraphqlBody } from './body'; +export { fromOpenCollectionVariables, toOpenCollectionVariables } from './variables'; +export { fromOpenCollectionActions, toOpenCollectionActions } from './actions'; +export { fromOpenCollectionScripts, toOpenCollectionScripts } from './scripts'; +export { fromOpenCollectionAssertions, toOpenCollectionAssertions } from './assertions'; diff --git a/packages/bruno-converters/src/opencollection/common/params.ts b/packages/bruno-converters/src/opencollection/common/params.ts new file mode 100644 index 000000000..53a69b6d2 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/params.ts @@ -0,0 +1,47 @@ +import { uuid } from '../../common/index.js'; +import type { + HttpRequestParam, + BrunoHttpRequestParam, + BrunoHttpRequestParamType +} from '../types'; + +export const fromOpenCollectionParams = (params: HttpRequestParam[] | undefined): BrunoHttpRequestParam[] => { + if (!params?.length) { + return []; + } + + return params.map((param): BrunoHttpRequestParam => ({ + uid: uuid(), + name: param.name || '', + value: param.value || '', + description: typeof param.description === 'string' ? param.description : param.description?.content || null, + type: (param.type || 'query') as BrunoHttpRequestParamType, + enabled: param.disabled !== true + })); +}; + +export const toOpenCollectionParams = (params: BrunoHttpRequestParam[] | null | undefined): HttpRequestParam[] | undefined => { + if (!params?.length) { + return undefined; + } + + const ocParams = params.map((param): HttpRequestParam => { + const httpParam: HttpRequestParam = { + name: param.name || '', + value: param.value || '', + type: param.type || 'query' + }; + + if (param.description && typeof param.description === 'string' && param.description.trim().length) { + httpParam.description = param.description; + } + + if (param.enabled === false) { + httpParam.disabled = true; + } + + return httpParam; + }); + + return ocParams.length ? ocParams : undefined; +}; diff --git a/packages/bruno-converters/src/opencollection/common/scripts.ts b/packages/bruno-converters/src/opencollection/common/scripts.ts new file mode 100644 index 000000000..ef05083ed --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/scripts.ts @@ -0,0 +1,64 @@ +import type { Scripts, Script } from '@opencollection/types/common/scripts'; +import type { FolderRequest as BrunoFolderRequest } from '@usebruno/schema-types/collection/folder'; +import type { HttpRequest as BrunoHttpRequest } from '@usebruno/schema-types/requests/http'; +import type { WebSocketRequest as BrunoWebSocketRequest } from '@usebruno/schema-types/requests/websocket'; +import type { GrpcRequest as BrunoGrpcRequest } from '@usebruno/schema-types/requests/grpc'; + +export const toOpenCollectionScripts = (request: BrunoFolderRequest | BrunoHttpRequest | BrunoWebSocketRequest | BrunoGrpcRequest | null | undefined): Scripts | undefined => { + const ocScripts: Scripts = []; + + if (request?.script?.req?.trim().length) { + ocScripts.push({ + type: 'before-request', + code: request.script.req.trim() + }); + } + if (request?.script?.res?.trim().length) { + ocScripts.push({ + type: 'after-response', + code: request.script.res.trim() + }); + } + if (request?.tests?.trim().length) { + ocScripts.push({ + type: 'tests', + code: request.tests.trim() + }); + } + + return ocScripts.length > 0 ? ocScripts : undefined; +}; + +export const fromOpenCollectionScripts = (scripts: Scripts | null | undefined): { + script?: { req?: string | null; res?: string | null }; + tests?: string | null; +} | undefined => { + if (!scripts || !Array.isArray(scripts) || scripts.length === 0) { + return undefined; + } + + const brunoScripts: { + script?: { req?: string | null; res?: string | null }; + tests?: string | null; + } = {}; + + for (const script of scripts) { + if (script.type === 'before-request' && script.code) { + if (!brunoScripts.script) { + brunoScripts.script = {}; + } + brunoScripts.script.req = script.code; + } + if (script.type === 'after-response' && script.code) { + if (!brunoScripts.script) { + brunoScripts.script = {}; + } + brunoScripts.script.res = script.code; + } + if (script.type === 'tests' && script.code) { + brunoScripts.tests = script.code; + } + } + + return Object.keys(brunoScripts).length > 0 ? brunoScripts : undefined; +}; diff --git a/packages/bruno-converters/src/opencollection/common/variables.ts b/packages/bruno-converters/src/opencollection/common/variables.ts new file mode 100644 index 000000000..e8e8687dd --- /dev/null +++ b/packages/bruno-converters/src/opencollection/common/variables.ts @@ -0,0 +1,75 @@ +import { uuid } from '../../common/index.js'; +import type { + Variable, + BrunoVariable, + BrunoVariables +} from '../types'; + +interface BrunoVars { + req: BrunoVariables; + res: BrunoVariables; +} + +export const fromOpenCollectionVariables = (variables: Variable[] | undefined): BrunoVars => { + if (!variables?.length) { + return { req: [], res: [] }; + } + + const reqVars: BrunoVariable[] = []; + + variables.forEach((v: Variable) => { + let value = ''; + if (typeof v.value === 'string') { + value = v.value; + } else if (v.value && typeof v.value === 'object' && 'data' in v.value) { + value = (v.value as { data: string }).data || ''; + } + + const variable: BrunoVariable = { + uid: uuid(), + name: v.name || '', + value, + enabled: v.disabled !== true, + local: false + }; + + if (v.description) { + variable.description = typeof v.description === 'string' ? v.description : (v.description as { content?: string })?.content || ''; + } + + reqVars.push(variable); + }); + + return { req: reqVars, res: [] }; +}; + +export const toOpenCollectionVariables = (vars: BrunoVars | { req?: BrunoVariables; res?: BrunoVariables } | null | undefined): Variable[] | undefined => { + // Handle folder variables (has req/res structure) - only use req vars + const hasReqRes = vars && 'req' in vars; + const reqVars = hasReqRes ? vars.req : vars as BrunoVariables; + + const reqVarsArray = Array.isArray(reqVars) ? reqVars : []; + + if (!reqVarsArray.length) { + return undefined; + } + + const ocVariables: Variable[] = reqVarsArray.map((v: BrunoVariable): Variable => { + const variable: Variable = { + name: v.name || '', + value: v.value || '' + }; + + if (v.description && typeof v.description === 'string' && v.description.trim().length) { + variable.description = v.description; + } + + if (v.enabled === false) { + variable.disabled = true; + } + + return variable; + }); + + return ocVariables.length > 0 ? ocVariables : undefined; +}; diff --git a/packages/bruno-converters/src/opencollection/environment.ts b/packages/bruno-converters/src/opencollection/environment.ts new file mode 100644 index 000000000..05d4c0d16 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/environment.ts @@ -0,0 +1,79 @@ +import { uuid } from '../common/index.js'; +import type { + Environment, + Variable, + BrunoEnvironment, + BrunoEnvironmentVariable +} from './types'; + +interface OCVariable extends Omit { + name: string; + value?: string | { data: string }; + secret?: boolean; + disabled?: boolean; +} + +export const fromOpenCollectionEnvironments = (environments: Environment[] | undefined): BrunoEnvironment[] => { + if (!environments?.length) { + return []; + } + + return environments.map((env): BrunoEnvironment => ({ + uid: uuid(), + name: env.name || 'Untitled Environment', + variables: (env.variables || []).map((v): BrunoEnvironmentVariable => { + const variable = v as OCVariable; + const isSecret = variable.secret === true; + + let value = ''; + if (!isSecret && variable.value !== undefined) { + if (typeof variable.value === 'string') { + value = variable.value; + } else if (variable.value && typeof variable.value === 'object' && 'data' in variable.value) { + value = variable.value.data; + } + } + + return { + uid: uuid(), + name: variable.name || '', + value, + type: 'text', + enabled: variable.disabled !== true, + secret: isSecret + }; + }) + })); +}; + +export const toOpenCollectionEnvironments = (environments: BrunoEnvironment[] | undefined): Environment[] | undefined => { + if (!environments?.length) { + return undefined; + } + + return environments.map((env): Environment => { + const ocEnv: Environment = { + name: env.name || 'Untitled Environment', + variables: (env.variables || []).map((v): OCVariable => { + const ocVar: OCVariable = { + name: v.name || '', + value: typeof v.value === 'string' ? v.value : String(v.value ?? '') + }; + + if (v.secret) { + ocVar.secret = true; + // Secret variables don't include the value in export + delete ocVar.value; + } + + if (v.enabled === false) { + ocVar.disabled = true; + } + + return ocVar; + }) as Variable[] + }; + + return ocEnv; + }); +}; diff --git a/packages/bruno-converters/src/opencollection/folder.ts b/packages/bruno-converters/src/opencollection/folder.ts new file mode 100644 index 000000000..b966616fc --- /dev/null +++ b/packages/bruno-converters/src/opencollection/folder.ts @@ -0,0 +1,135 @@ +import { uuid } from '../common/index.js'; +import { + fromOpenCollectionHeaders, + toOpenCollectionHeaders, + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionVariables, + toOpenCollectionVariables +} from './common'; +import { fromOpenCollectionItems, toOpenCollectionItems } from './items'; +import type { + Folder, + FolderInfo, + RequestDefaults, + Auth, + BrunoItem, + BrunoFolderRoot, + BrunoKeyValue +} from './types'; + +export const fromOpenCollectionFolder = (folder: Folder): BrunoItem => { + const info = folder.info || {}; + + const brunoFolder: BrunoItem = { + uid: uuid(), + type: 'folder', + name: info.name || 'Untitled Folder', + seq: info.seq || 1 + }; + + if (folder.request || folder.docs) { + const root: BrunoFolderRoot = {}; + + if (folder.request) { + const scripts = fromOpenCollectionScripts(folder.request.scripts); + root.request = { + headers: fromOpenCollectionHeaders(folder.request.headers), + auth: fromOpenCollectionAuth(folder.request.auth as Auth), + script: scripts?.script, + vars: fromOpenCollectionVariables(folder.request.variables), + tests: scripts?.tests + }; + } + + if (folder.docs) { + if (typeof folder.docs === 'string') { + root.docs = folder.docs; + } else if (folder.docs && typeof folder.docs === 'object' && 'content' in folder.docs) { + root.docs = folder.docs.content || ''; + } + } + + root.meta = { + name: info.name || 'Untitled Folder', + seq: info.seq || 1 + }; + + brunoFolder.root = root; + } + + if (info.tags?.length) { + brunoFolder.tags = info.tags; + } + + if (folder.items?.length) { + brunoFolder.items = fromOpenCollectionItems(folder.items, fromOpenCollectionFolder as (f: unknown) => BrunoItem); + } + + return brunoFolder; +}; + +export const toOpenCollectionFolder = (folder: BrunoItem): Folder => { + const info: FolderInfo = { + name: folder.name || 'Untitled Folder', + type: 'folder' + }; + + if (folder.seq) { + info.seq = folder.seq; + } + + if (folder.tags?.length) { + info.tags = folder.tags; + } + + const ocFolder: Folder = { + info + }; + + if (folder.root) { + const folderRequest = folder.root.request || {}; + + const headers = toOpenCollectionHeaders(folderRequest.headers as BrunoKeyValue[]); + const auth = toOpenCollectionAuth(folderRequest.auth); + const scripts = toOpenCollectionScripts(folderRequest as { script?: { req: string | null; res: string | null } | null; tests?: string | null }); + const variables = toOpenCollectionVariables(folderRequest.vars); + + if (headers || auth || scripts || variables) { + const request: RequestDefaults = {}; + + if (headers) { + request.headers = headers; + } + + if (auth) { + request.auth = auth; + } + + if (scripts) { + request.scripts = scripts; + } + + if (variables) { + request.variables = variables; + } + + ocFolder.request = request; + } + + if (folder.root.docs) { + ocFolder.docs = { + content: folder.root.docs, + type: 'text/markdown' + }; + } + } + + if (folder.items?.length) { + ocFolder.items = toOpenCollectionItems(folder.items, toOpenCollectionFolder as (f: BrunoItem) => unknown) as Folder['items']; + } + + return ocFolder; +}; diff --git a/packages/bruno-converters/src/opencollection/index.ts b/packages/bruno-converters/src/opencollection/index.ts new file mode 100644 index 000000000..923c681a6 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/index.ts @@ -0,0 +1,37 @@ +export { openCollectionToBruno } from './opencollection-to-bruno'; +export { brunoToOpenCollection } from './bruno-to-opencollection'; +export { fromOpenCollectionFolder, toOpenCollectionFolder } from './folder'; +export { fromOpenCollectionEnvironments, toOpenCollectionEnvironments } from './environment'; + +export { + fromOpenCollectionItem, + toOpenCollectionItem, + fromOpenCollectionItems, + toOpenCollectionItems, + fromOpenCollectionHttpItem, + toOpenCollectionHttpItem, + fromOpenCollectionGraphqlItem, + toOpenCollectionGraphqlItem, + fromOpenCollectionGrpcItem, + toOpenCollectionGrpcItem, + fromOpenCollectionWebsocketItem, + toOpenCollectionWebsocketItem +} from './items'; + +export { + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionHeaders, + toOpenCollectionHeaders, + fromOpenCollectionParams, + toOpenCollectionParams, + fromOpenCollectionBody, + toOpenCollectionBody, + toOpenCollectionGraphqlBody, + fromOpenCollectionVariables, + toOpenCollectionVariables, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionAssertions, + toOpenCollectionAssertions +} from './common'; diff --git a/packages/bruno-converters/src/opencollection/items/graphql.ts b/packages/bruno-converters/src/opencollection/items/graphql.ts new file mode 100644 index 000000000..01a1d6f2e --- /dev/null +++ b/packages/bruno-converters/src/opencollection/items/graphql.ts @@ -0,0 +1,192 @@ +import { uuid } from '../../common/index.js'; +import { + fromOpenCollectionHeaders, + toOpenCollectionHeaders, + fromOpenCollectionParams, + toOpenCollectionParams, + fromOpenCollectionBody, + toOpenCollectionGraphqlBody, + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionVariables, + toOpenCollectionVariables, + fromOpenCollectionActions, + toOpenCollectionActions, + fromOpenCollectionAssertions, + toOpenCollectionAssertions +} from '../common'; +import type { + GraphQLRequest, + GraphQLRequestInfo, + GraphQLRequestDetails, + GraphQLRequestRuntime, + GraphQLRequestSettings, + GraphQLBody, + GraphQLBodyVariant, + Auth, + BrunoItem, + BrunoKeyValue, + BrunoHttpRequestParam +} from '../types'; + +const getGraphqlBody = (body: GraphQLBody | GraphQLBodyVariant[] | undefined): GraphQLBody | undefined => { + if (!body) return undefined; + if (Array.isArray(body)) { + const selected = body.find((v) => v.selected); + return selected?.body || body[0]?.body; + } + return body; +}; + +export const fromOpenCollectionGraphqlItem = (item: GraphQLRequest): BrunoItem => { + const info = item.info || {}; + const graphql = item.graphql || {}; + const runtime = item.runtime || {}; + + const scripts = fromOpenCollectionScripts(runtime.scripts); + const graphqlBody = getGraphqlBody(graphql.body); + + // variables (pre-request from variables, post-response from actions) + const variables = fromOpenCollectionVariables(runtime.variables); + const postResponseVars = fromOpenCollectionActions(runtime.actions); + + const brunoItem: BrunoItem = { + uid: uuid(), + type: 'graphql-request', + name: info.name || 'Untitled Request', + seq: info.seq || 1, + request: { + url: graphql.url || '', + method: graphql.method || 'POST', + headers: fromOpenCollectionHeaders(graphql.headers), + params: fromOpenCollectionParams(graphql.params), + body: fromOpenCollectionBody(graphqlBody, 'graphql'), + auth: fromOpenCollectionAuth(runtime.auth as Auth), + script: scripts?.script, + vars: { + req: variables.req, + res: postResponseVars + }, + assertions: fromOpenCollectionAssertions(runtime.assertions), + tests: scripts?.tests, + docs: item.docs || '' + } + }; + + const settings = item.settings; + if (settings) { + brunoItem.settings = {}; + if (settings.encodeUrl !== undefined) { + (brunoItem.settings as Record).encodeUrl = settings.encodeUrl; + } + if (settings.timeout !== undefined) { + (brunoItem.settings as Record).timeout = settings.timeout; + } + if (settings.followRedirects !== undefined) { + (brunoItem.settings as Record).followRedirects = settings.followRedirects; + } + if (settings.maxRedirects !== undefined) { + (brunoItem.settings as Record).maxRedirects = settings.maxRedirects; + } + } + + if (info.tags?.length) { + brunoItem.tags = info.tags; + } + + return brunoItem; +}; + +export const toOpenCollectionGraphqlItem = (item: BrunoItem): GraphQLRequest => { + const request = (item.request || {}) as Record; + const brunoSettings = (item.settings || {}) as Record; + + const info: GraphQLRequestInfo = { + name: item.name || 'Untitled Request', + type: 'graphql' + }; + + if (item.seq) { + info.seq = item.seq; + } + + if (item.tags?.length) { + info.tags = item.tags; + } + + const graphql: GraphQLRequestDetails = { + url: request.url as string || '', + method: request.method as string || 'POST' + }; + + const headers = toOpenCollectionHeaders(request.headers as BrunoKeyValue[]); + if (headers) { + graphql.headers = headers; + } + + const params = toOpenCollectionParams(request.params as BrunoHttpRequestParam[]); + if (params) { + graphql.params = params; + } + + const body = toOpenCollectionGraphqlBody(request.body as Parameters[0]); + if (body) { + graphql.body = body; + } + + const ocRequest: GraphQLRequest = { + info, + graphql + }; + + const auth = toOpenCollectionAuth(request.auth as Parameters[0]); + const scripts = toOpenCollectionScripts(request as Parameters[0]); + const variables = toOpenCollectionVariables(request.vars as Parameters[0]); + const assertions = toOpenCollectionAssertions(request.assertions as BrunoKeyValue[]); + + // actions (from post-response variables) + const vars = request.vars as { req?: unknown[]; res?: unknown[] } | undefined; + const actions = toOpenCollectionActions(vars?.res as Parameters[0]); + + if (auth || scripts || variables || assertions || actions) { + const runtime: GraphQLRequestRuntime = {}; + + if (auth) { + runtime.auth = auth; + } + + if (scripts) { + runtime.scripts = scripts; + } + + if (variables) { + runtime.variables = variables; + } + + if (assertions) { + runtime.assertions = assertions; + } + + if (actions) { + runtime.actions = actions; + } + + ocRequest.runtime = runtime; + } + + const settings: GraphQLRequestSettings = { + encodeUrl: typeof brunoSettings.encodeUrl === 'boolean' ? brunoSettings.encodeUrl : true, + timeout: typeof brunoSettings.timeout === 'number' ? brunoSettings.timeout : 0, + followRedirects: typeof brunoSettings.followRedirects === 'boolean' ? brunoSettings.followRedirects : true, + maxRedirects: typeof brunoSettings.maxRedirects === 'number' ? brunoSettings.maxRedirects : 5 + }; + ocRequest.settings = settings; + + if (request.docs) { + ocRequest.docs = request.docs as string; + } + + return ocRequest; +}; diff --git a/packages/bruno-converters/src/opencollection/items/grpc.ts b/packages/bruno-converters/src/opencollection/items/grpc.ts new file mode 100644 index 000000000..9da71dc4c --- /dev/null +++ b/packages/bruno-converters/src/opencollection/items/grpc.ts @@ -0,0 +1,214 @@ +import { uuid } from '../../common/index.js'; +import { + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionVariables, + toOpenCollectionVariables, + fromOpenCollectionActions, + toOpenCollectionActions, + fromOpenCollectionAssertions, + toOpenCollectionAssertions +} from '../common'; +import type { + GrpcRequest, + GrpcRequestInfo, + GrpcRequestDetails, + GrpcRequestRuntime, + GrpcMetadata, + GrpcMessageVariant, + GrpcMessagePayload, + Auth, + BrunoItem, + BrunoGrpcMessage, + BrunoKeyValue +} from '../types'; + +const fromGrpcMetadata = (metadata: GrpcMetadata[] | undefined): BrunoKeyValue[] => { + if (!metadata?.length) { + return []; + } + + return metadata.map((m): BrunoKeyValue => ({ + uid: uuid(), + name: m.name || '', + value: m.value || '', + description: typeof m.description === 'string' ? m.description : (m.description as { content?: string } | undefined)?.content || null, + enabled: m.disabled !== true + })); +}; + +export const fromOpenCollectionGrpcItem = (item: GrpcRequest): BrunoItem => { + const info = item.info || {}; + const grpc = item.grpc || {}; + const runtime = item.runtime || {}; + + const grpcMessages: BrunoGrpcMessage[] = []; + + if (grpc.message) { + if (typeof grpc.message === 'string') { + grpcMessages.push({ name: 'message 1', content: grpc.message }); + } else if (Array.isArray(grpc.message)) { + grpc.message.forEach((msg, index) => { + grpcMessages.push({ + name: msg.title || `message ${index + 1}`, + content: typeof msg.message === 'string' ? msg.message : '' + }); + }); + } + } + + const scripts = fromOpenCollectionScripts(runtime.scripts); + + // variables (pre-request from variables, post-response from actions) + const variables = fromOpenCollectionVariables(runtime.variables); + const postResponseVars = fromOpenCollectionActions((runtime as { actions?: Parameters[0] }).actions); + + const brunoItem: BrunoItem = { + uid: uuid(), + type: 'grpc-request', + name: info.name || 'Untitled Request', + seq: info.seq || 1, + request: { + url: grpc.url || '', + method: grpc.method || '', + headers: fromGrpcMetadata(grpc.metadata), + body: { + mode: 'grpc', + grpc: grpcMessages + }, + auth: fromOpenCollectionAuth(runtime.auth as Auth), + script: scripts?.script, + vars: { + req: variables.req, + res: postResponseVars + }, + assertions: fromOpenCollectionAssertions(runtime.assertions), + tests: scripts?.tests, + docs: '' + } + }; + + // Add grpc-specific properties + if (grpc.methodType) { + (brunoItem.request as unknown as Record).methodType = grpc.methodType; + } + if (grpc.protoFilePath) { + (brunoItem.request as unknown as Record).protoPath = grpc.protoFilePath; + } + + if (info.tags?.length) { + brunoItem.tags = info.tags; + } + + return brunoItem; +}; + +export const toOpenCollectionGrpcItem = (item: BrunoItem): GrpcRequest => { + const request = (item.request || {}) as Record; + + const info: GrpcRequestInfo = { + name: item.name || 'Untitled Request', + type: 'grpc' + }; + + if (item.seq) { + info.seq = item.seq; + } + + if (item.tags?.length) { + info.tags = item.tags; + } + + const grpc: GrpcRequestDetails = { + url: request.url as string || '', + method: request.method as string || '' + }; + + if (request.methodType) { + grpc.methodType = request.methodType as GrpcRequestDetails['methodType']; + } + if (request.protoPath) { + grpc.protoFilePath = request.protoPath as string; + } + + const headers = request.headers as BrunoKeyValue[] | undefined; + if (headers?.length) { + grpc.metadata = headers.map((h): GrpcMetadata => { + const metadata: GrpcMetadata = { + name: h.name || '', + value: h.value || '' + }; + + if (h.description && typeof h.description === 'string' && h.description.trim().length) { + metadata.description = h.description; + } + + if (h.enabled === false) { + metadata.disabled = true; + } + + return metadata; + }); + } + + const body = request.body as { grpc?: BrunoGrpcMessage[] } | undefined; + if (body?.grpc?.length) { + const messages = body.grpc; + if (messages.length === 1) { + grpc.message = messages[0].content || ''; + } else { + grpc.message = messages.map((msg): GrpcMessageVariant => ({ + title: msg.name || 'Untitled', + message: msg.content || '' + })); + } + } + + const ocRequest: GrpcRequest = { + info, + grpc + }; + + const auth = toOpenCollectionAuth(request.auth as Parameters[0]); + const scripts = toOpenCollectionScripts(request as Parameters[0]); + const variables = toOpenCollectionVariables(request.vars as Parameters[0]); + const assertions = toOpenCollectionAssertions(request.assertions as BrunoKeyValue[]); + + // actions (from post-response variables) + const vars = request.vars as { req?: unknown[]; res?: unknown[] } | undefined; + const actions = toOpenCollectionActions(vars?.res as Parameters[0]); + + if (auth || scripts || variables || assertions || actions) { + const runtime: GrpcRequestRuntime = {}; + + if (auth) { + runtime.auth = auth; + } + + if (scripts) { + runtime.scripts = scripts; + } + + if (variables) { + runtime.variables = variables; + } + + if (assertions) { + runtime.assertions = assertions; + } + + if (actions) { + (runtime as { actions?: typeof actions }).actions = actions; + } + + ocRequest.runtime = runtime; + } + + if (request.docs) { + ocRequest.docs = request.docs as string; + } + + return ocRequest; +}; diff --git a/packages/bruno-converters/src/opencollection/items/http.ts b/packages/bruno-converters/src/opencollection/items/http.ts new file mode 100644 index 000000000..6bdc28421 --- /dev/null +++ b/packages/bruno-converters/src/opencollection/items/http.ts @@ -0,0 +1,295 @@ +import { uuid } from '../../common/index.js'; +import { + fromOpenCollectionHeaders, + toOpenCollectionHeaders, + fromOpenCollectionParams, + toOpenCollectionParams, + fromOpenCollectionBody, + toOpenCollectionBody, + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionVariables, + toOpenCollectionVariables, + fromOpenCollectionActions, + toOpenCollectionActions, + fromOpenCollectionAssertions, + toOpenCollectionAssertions +} from '../common'; +import type { + HttpRequest, + HttpRequestSettings, + HttpRequestExample, + HttpRequestInfo, + HttpRequestDetails, + HttpRequestRuntime, + HttpRequestHeader, + HttpRequestBody, + Auth, + BrunoItem, + BrunoKeyValue, + BrunoHttpRequestParam, + BrunoExample, + BrunoHttpRequest +} from '../types'; +import type { HttpItemSettings as BrunoHttpItemSettings } from '@usebruno/schema-types/collection/item'; + +const getHttpBody = (body: HttpRequestBody | Array<{ title: string; selected?: boolean; body: HttpRequestBody }> | undefined): HttpRequestBody | undefined => { + if (!body) return undefined; + if (Array.isArray(body)) { + const selected = body.find((v) => v.selected); + return selected?.body || body[0]?.body; + } + return body; +}; + +export const fromOpenCollectionHttpItem = (ocRequest: HttpRequest): BrunoItem => { + const info = ocRequest.info; + const http = ocRequest.http; + const runtime = ocRequest.runtime; + + const scripts = fromOpenCollectionScripts(runtime?.scripts); + const httpBody = getHttpBody(http?.body as HttpRequestBody); + + // variables (pre-request from variables, post-response from actions) + const variables = fromOpenCollectionVariables(runtime?.variables); + const postResponseVars = fromOpenCollectionActions(runtime?.actions); + + const brunoRequest: BrunoHttpRequest = { + url: http?.url || '', + method: http?.method || 'GET', + headers: fromOpenCollectionHeaders(http?.headers) || [], + params: fromOpenCollectionParams(http?.params) || [], + body: fromOpenCollectionBody(httpBody) || { + mode: 'none', + json: null, + text: null, + xml: null, + sparql: null, + formUrlEncoded: [], + multipartForm: [], + graphql: null, + file: [] + }, + auth: fromOpenCollectionAuth(runtime?.auth as Auth), + script: { + req: scripts?.script?.req || null, + res: scripts?.script?.res || null + }, + vars: { + req: variables.req, + res: postResponseVars + }, + assertions: fromOpenCollectionAssertions(runtime?.assertions) || [], + tests: scripts?.tests || null, + docs: ocRequest.docs || null + }; + + const brunoItem: BrunoItem = { + uid: uuid(), + type: 'http-request', + seq: info?.seq || 1, + name: info?.name || 'Untitled Request', + tags: info?.tags || [], + request: brunoRequest, + settings: null, + fileContent: null, + root: null, + items: [], + examples: [], + filename: null, + pathname: null + }; + + if (ocRequest.settings) { + const settings: BrunoHttpItemSettings = { + encodeUrl: typeof ocRequest.settings.encodeUrl === 'boolean' ? ocRequest.settings.encodeUrl : true, + timeout: typeof ocRequest.settings.timeout === 'number' ? ocRequest.settings.timeout : 0, + followRedirects: typeof ocRequest.settings.followRedirects === 'boolean' ? ocRequest.settings.followRedirects : true, + maxRedirects: typeof ocRequest.settings.maxRedirects === 'number' ? ocRequest.settings.maxRedirects : 5 + }; + brunoItem.settings = settings; + } + + if (ocRequest.examples?.length) { + brunoItem.examples = ocRequest.examples.map((example): BrunoExample => ({ + uid: uuid(), + itemUid: brunoItem.uid, + name: example.name || 'Untitled Example', + description: typeof example.description === 'string' ? example.description : (example.description as { content?: string })?.content || null, + type: 'http-request', + request: { + url: example.request?.url || '', + method: example.request?.method || 'GET', + headers: fromOpenCollectionHeaders(example.request?.headers) || [], + params: fromOpenCollectionParams(example.request?.params) || [], + body: fromOpenCollectionBody(example.request?.body) || null + }, + response: example.response ? { + status: String(example.response.status || 200), + statusText: example.response.statusText || 'OK', + headers: fromOpenCollectionHeaders(example.response.headers as HttpRequestHeader[]) || [], + body: example.response.body ? { + type: example.response.body.type || 'text', + content: example.response.body.data || '' + } : null + } : null + })); + } + + return brunoItem; +}; + +export const toOpenCollectionHttpItem = (item: BrunoItem): HttpRequest => { + const ocRequest: HttpRequest = {}; + const brunoRequest = item.request as BrunoHttpRequest; + const brunoSettings = item.settings as BrunoHttpItemSettings | undefined; + + const info: HttpRequestInfo = { + name: item.name || 'Untitled Request', + type: 'http' + }; + if (item.seq) { + info.seq = item.seq; + } + if (item.tags?.length) { + info.tags = item.tags; + } + ocRequest.info = info; + + const http: HttpRequestDetails = { + method: brunoRequest?.method || 'GET', + url: brunoRequest?.url || '' + }; + + const headers = toOpenCollectionHeaders(brunoRequest?.headers as BrunoKeyValue[]); + if (headers) { + http.headers = headers; + } + + const params = toOpenCollectionParams(brunoRequest?.params as BrunoHttpRequestParam[]); + if (params) { + http.params = params; + } + + const body = toOpenCollectionBody(brunoRequest?.body); + if (body) { + http.body = body; + } + + ocRequest.http = http; + + const runtime: HttpRequestRuntime = {}; + let hasRuntime = false; + + const variables = toOpenCollectionVariables(brunoRequest?.vars); + if (variables) { + runtime.variables = variables; + hasRuntime = true; + } + + const scripts = toOpenCollectionScripts(brunoRequest); + if (scripts) { + runtime.scripts = scripts; + hasRuntime = true; + } + + const assertions = toOpenCollectionAssertions(brunoRequest?.assertions as BrunoKeyValue[]); + if (assertions) { + runtime.assertions = assertions; + hasRuntime = true; + } + + // actions (from post-response variables) + const resVars = brunoRequest?.vars?.res; + const actions = toOpenCollectionActions(resVars); + if (actions) { + runtime.actions = actions; + hasRuntime = true; + } + + const auth = toOpenCollectionAuth(brunoRequest?.auth); + if (auth) { + runtime.auth = auth; + hasRuntime = true; + } + + if (hasRuntime) { + ocRequest.runtime = runtime; + } + + const settings: HttpRequestSettings = { + encodeUrl: typeof brunoSettings?.encodeUrl === 'boolean' ? brunoSettings.encodeUrl : true, + timeout: typeof brunoSettings?.timeout === 'number' ? brunoSettings.timeout : 0, + followRedirects: typeof brunoSettings?.followRedirects === 'boolean' ? brunoSettings.followRedirects : true, + maxRedirects: typeof brunoSettings?.maxRedirects === 'number' ? brunoSettings.maxRedirects : 5 + }; + ocRequest.settings = settings; + + if (brunoRequest?.docs) { + ocRequest.docs = brunoRequest.docs; + } + + if (item.examples?.length) { + ocRequest.examples = item.examples.map((example): HttpRequestExample => { + const ocExample: HttpRequestExample = { + name: example.name || 'Untitled Example' + }; + + if (example.description) { + ocExample.description = example.description; + } + + if (example.request) { + ocExample.request = { + url: example.request.url || '', + method: example.request.method || 'GET' + }; + + const exampleHeaders = toOpenCollectionHeaders(example.request.headers as BrunoKeyValue[]); + if (exampleHeaders) { + ocExample.request.headers = exampleHeaders; + } + + const exampleParams = toOpenCollectionParams(example.request.params as BrunoHttpRequestParam[]); + if (exampleParams) { + ocExample.request.params = exampleParams; + } + + const exampleBody = toOpenCollectionBody(example.request.body); + if (exampleBody) { + ocExample.request.body = exampleBody; + } + } + + if (example.response) { + ocExample.response = {}; + + if (example.response.status !== undefined) { + ocExample.response.status = Number(example.response.status); + } + + if (example.response.statusText) { + ocExample.response.statusText = example.response.statusText; + } + + const responseHeaders = toOpenCollectionHeaders(example.response.headers as BrunoKeyValue[]); + if (responseHeaders) { + ocExample.response.headers = responseHeaders; + } + + if (example.response.body) { + ocExample.response.body = { + type: (example.response.body.type as 'json' | 'text' | 'xml' | 'html' | 'binary') || 'text', + data: String(example.response.body.content || '') + }; + } + } + + return ocExample; + }); + } + + return ocRequest; +}; diff --git a/packages/bruno-converters/src/opencollection/items/index.ts b/packages/bruno-converters/src/opencollection/items/index.ts new file mode 100644 index 000000000..44d10072a --- /dev/null +++ b/packages/bruno-converters/src/opencollection/items/index.ts @@ -0,0 +1,125 @@ +import { uuid } from '../../common/index.js'; +import { fromOpenCollectionHttpItem, toOpenCollectionHttpItem } from './http'; +import { fromOpenCollectionGraphqlItem, toOpenCollectionGraphqlItem } from './graphql'; +import { fromOpenCollectionGrpcItem, toOpenCollectionGrpcItem } from './grpc'; +import { fromOpenCollectionWebsocketItem, toOpenCollectionWebsocketItem } from './websocket'; +import type { + BrunoItem +} from '../types'; + +interface OCItem { + info?: { + type?: string; + name?: string; + seq?: number; + }; + http?: unknown; + graphql?: unknown; + grpc?: unknown; + websocket?: unknown; + items?: unknown[]; + script?: string; +} + +const getItemType = (item: OCItem): string => { + if (item.info?.type) { + return item.info.type; + } + + if ('items' in item && item.items) { + return 'folder'; + } + + if ('http' in item && item.http) { + return 'http'; + } + + if ('graphql' in item && item.graphql) { + return 'graphql'; + } + + if ('grpc' in item && item.grpc) { + return 'grpc'; + } + + if ('websocket' in item && item.websocket) { + return 'websocket'; + } + + if ('script' in item && typeof item.script === 'string') { + return 'script'; + } + + return 'unknown'; +}; + +export const fromOpenCollectionItem = (item: unknown, parseFolder: (folder: unknown) => BrunoItem): BrunoItem | null => { + const ocItem = item as OCItem; + const itemType = getItemType(ocItem); + + switch (itemType) { + case 'http': + return fromOpenCollectionHttpItem(item as Parameters[0]); + case 'graphql': + return fromOpenCollectionGraphqlItem(item as Parameters[0]); + case 'grpc': + return fromOpenCollectionGrpcItem(item as Parameters[0]); + case 'websocket': + return fromOpenCollectionWebsocketItem(item as Parameters[0]); + case 'folder': + return parseFolder(item); + case 'script': { + const scriptItem = item as { script?: string; info?: { name?: string } }; + return { + uid: uuid(), + type: 'js', + name: scriptItem.info?.name || 'script.js', + fileContent: scriptItem.script || '' + }; + } + default: + return null; + } +}; + +export const toOpenCollectionItem = (item: BrunoItem, stringifyFolder: (folder: BrunoItem) => unknown): unknown | null => { + switch (item.type) { + case 'http-request': + return toOpenCollectionHttpItem(item); + case 'graphql-request': + return toOpenCollectionGraphqlItem(item); + case 'grpc-request': + return toOpenCollectionGrpcItem(item); + case 'ws-request': + return toOpenCollectionWebsocketItem(item); + case 'folder': + return stringifyFolder(item); + case 'js': + return { + info: { + name: item.name || 'script.js', + type: 'script' + }, + script: item.fileContent || '' + }; + default: + return null; + } +}; + +export const fromOpenCollectionItems = (items: unknown[] | undefined, parseFolder: (folder: unknown) => BrunoItem): BrunoItem[] => { + return (items || []) + .map((item) => fromOpenCollectionItem(item, parseFolder)) + .filter((item): item is BrunoItem => item !== null); +}; + +export const toOpenCollectionItems = (items: BrunoItem[] | undefined | null, stringifyFolder: (folder: BrunoItem) => unknown): unknown[] => { + return (items || []) + .map((item) => toOpenCollectionItem(item, stringifyFolder)) + .filter((item): item is unknown => item !== null); +}; + +export { fromOpenCollectionHttpItem, toOpenCollectionHttpItem } from './http'; +export { fromOpenCollectionGraphqlItem, toOpenCollectionGraphqlItem } from './graphql'; +export { fromOpenCollectionGrpcItem, toOpenCollectionGrpcItem } from './grpc'; +export { fromOpenCollectionWebsocketItem, toOpenCollectionWebsocketItem } from './websocket'; diff --git a/packages/bruno-converters/src/opencollection/items/websocket.ts b/packages/bruno-converters/src/opencollection/items/websocket.ts new file mode 100644 index 000000000..5690e2a5e --- /dev/null +++ b/packages/bruno-converters/src/opencollection/items/websocket.ts @@ -0,0 +1,176 @@ +import { uuid } from '../../common/index.js'; +import { + fromOpenCollectionHeaders, + toOpenCollectionHeaders, + fromOpenCollectionAuth, + toOpenCollectionAuth, + fromOpenCollectionScripts, + toOpenCollectionScripts, + fromOpenCollectionVariables, + toOpenCollectionVariables, + fromOpenCollectionActions, + toOpenCollectionActions +} from '../common'; +import type { + WebSocketRequest, + WebSocketRequestInfo, + WebSocketRequestDetails, + WebSocketRequestRuntime, + WebSocketMessage, + WebSocketMessageVariant, + Auth, + BrunoItem, + BrunoWsMessage, + BrunoKeyValue, + BrunoWebSocketRequestBody +} from '../types'; + +export const fromOpenCollectionWebsocketItem = (item: WebSocketRequest): BrunoItem => { + const info = item.info || {}; + const websocket = item.websocket || {}; + const runtime = item.runtime || {}; + + const wsMessages: BrunoWsMessage[] = []; + + if (websocket.message) { + if ('type' in websocket.message && 'data' in websocket.message) { + const msg = websocket.message as WebSocketMessage; + wsMessages.push({ + name: 'message 1', + type: msg.type || 'json', + content: msg.data || '' + }); + } else if (Array.isArray(websocket.message)) { + websocket.message.forEach((m, index) => { + wsMessages.push({ + name: m.title || `message ${index + 1}`, + type: m.message?.type || 'json', + content: m.message?.data || '' + }); + }); + } + } + + const scripts = fromOpenCollectionScripts(runtime.scripts); + + // variables (pre-request from variables, post-response from actions) + const variables = fromOpenCollectionVariables(runtime.variables); + const postResponseVars = fromOpenCollectionActions((runtime as { actions?: Parameters[0] }).actions); + + const wsBody: BrunoWebSocketRequestBody = { + mode: 'ws', + ws: wsMessages + }; + + const brunoItem: BrunoItem = { + uid: uuid(), + type: 'ws-request', + name: info.name || 'Untitled Request', + seq: info.seq || 1, + request: { + url: websocket.url || '', + headers: fromOpenCollectionHeaders(websocket.headers), + body: wsBody, + auth: fromOpenCollectionAuth(runtime.auth as Auth), + script: scripts?.script, + vars: { + req: variables.req, + res: postResponseVars + }, + tests: scripts?.tests, + docs: item.docs || '' + } + }; + + if (info.tags?.length) { + brunoItem.tags = info.tags; + } + + return brunoItem; +}; + +export const toOpenCollectionWebsocketItem = (item: BrunoItem): WebSocketRequest => { + const request = (item.request || {}) as Record; + + const info: WebSocketRequestInfo = { + name: item.name || 'Untitled Request', + type: 'websocket' + }; + + if (item.seq) { + info.seq = item.seq; + } + + if (item.tags?.length) { + info.tags = item.tags; + } + + const websocket: WebSocketRequestDetails = { + url: request.url as string || '' + }; + + const headers = toOpenCollectionHeaders(request.headers as BrunoKeyValue[]); + if (headers) { + websocket.headers = headers; + } + + const body = request.body as { ws?: BrunoWsMessage[] } | undefined; + if (body?.ws?.length) { + const messages = body.ws; + if (messages.length === 1) { + websocket.message = { + type: (messages[0].type as WebSocketMessage['type']) || 'json', + data: messages[0].content || '' + }; + } else { + websocket.message = messages.map((msg): WebSocketMessageVariant => ({ + title: msg.name || 'Untitled', + message: { + type: (msg.type as WebSocketMessage['type']) || 'json', + data: msg.content || '' + } + })); + } + } + + const ocRequest: WebSocketRequest = { + info, + websocket + }; + + const auth = toOpenCollectionAuth(request.auth as Parameters[0]); + const scripts = toOpenCollectionScripts(request as Parameters[0]); + const variables = toOpenCollectionVariables(request.vars as Parameters[0]); + + // actions (from post-response variables) + const vars = request.vars as { req?: unknown[]; res?: unknown[] } | undefined; + const actions = toOpenCollectionActions(vars?.res as Parameters[0]); + + if (auth || scripts || variables || actions) { + const runtime: WebSocketRequestRuntime = {}; + + if (auth) { + runtime.auth = auth; + } + + if (scripts) { + runtime.scripts = scripts; + } + + if (variables) { + runtime.variables = variables; + } + + if (actions) { + (runtime as { actions?: typeof actions }).actions = actions; + } + + ocRequest.runtime = runtime; + } + + if (request.docs) { + ocRequest.docs = request.docs as string; + } + + return ocRequest; +}; diff --git a/packages/bruno-converters/src/opencollection/opencollection-to-bruno.ts b/packages/bruno-converters/src/opencollection/opencollection-to-bruno.ts new file mode 100644 index 000000000..f6ffba99e --- /dev/null +++ b/packages/bruno-converters/src/opencollection/opencollection-to-bruno.ts @@ -0,0 +1,133 @@ +import { OpenCollection } from "@opencollection/types"; +import { BrunoCollection, BrunoCollectionRoot, BrunoConfig, PemCertificate, Pkcs12Certificate } from "./types"; +import { fromOpenCollectionAuth, fromOpenCollectionHeaders, fromOpenCollectionScripts, fromOpenCollectionVariables } from "./common"; +import { uuid } from "../common"; +import { fromOpenCollectionItems } from "./items"; +import { fromOpenCollectionFolder } from "./folder"; +import { fromOpenCollectionEnvironments } from "./environment"; + +const fromOpenCollectionConfig = (oc: OpenCollection): BrunoConfig => { + const extensions = oc.extensions; + const ignoreList = extensions && Array.isArray(extensions.ignore) + ? extensions.ignore as string[] + : ['node_modules', '.git']; + + const brunoConfig: BrunoConfig = { + version: '1', + name: oc.info?.name || 'Untitled Collection', + type: 'collection', + ignore: ignoreList + }; + + const config = oc.config; + if (!config) { + return brunoConfig; + } + + if (config.protobuf) { + brunoConfig.protobuf = { + protoFiles: config.protobuf.protoFiles?.map((f) => ({ + path: f.path + })), + importPaths: config.protobuf.importPaths?.map((p) => ({ + path: p.path, + disabled: p.disabled || false + })) + }; + } + + if (config.proxy && typeof config.proxy !== 'boolean') { + if (config.proxy === 'inherit') { + brunoConfig.proxy = { enabled: 'global' }; + } else { + const proxyConfig = config.proxy; + brunoConfig.proxy = { + enabled: true, + protocol: proxyConfig.protocol || 'http', + hostname: proxyConfig.hostname || '', + port: proxyConfig.port || 0 + }; + + if (proxyConfig.auth) { + brunoConfig.proxy.auth = { + enabled: true, + username: proxyConfig.auth.username || '', + password: proxyConfig.auth.password || '' + }; + } + + if (proxyConfig.bypassProxy) { + brunoConfig.proxy.bypassProxy = proxyConfig.bypassProxy; + } + } + } + + if (config.clientCertificates?.length) { + brunoConfig.clientCertificates = { + certs: config.clientCertificates.map((cert) => { + if (cert.type === 'pem') { + const pemCert = cert as PemCertificate; + return { + domain: pemCert.domain || '', + type: 'pem' as const, + certFilePath: pemCert.certificateFilePath || '', + keyFilePath: pemCert.privateKeyFilePath || '', + passphrase: pemCert.passphrase || '' + }; + } else if (cert.type === 'pkcs12') { + const pkcs12Cert = cert as Pkcs12Certificate; + return { + domain: pkcs12Cert.domain || '', + type: 'pkcs12' as const, + pfxFilePath: pkcs12Cert.pkcs12FilePath || '', + passphrase: pkcs12Cert.passphrase || '' + }; + } + return null; + }).filter((cert): cert is NonNullable => cert !== null) + }; + } + + return brunoConfig; +}; + +const fromOpenCollectionRoot = (oc: OpenCollection): BrunoCollectionRoot => { + const root: BrunoCollectionRoot = {}; + + if (oc.request) { + const scripts = fromOpenCollectionScripts(oc.request.scripts); + root.request = { + headers: fromOpenCollectionHeaders(oc.request.headers), + auth: fromOpenCollectionAuth(oc.request.auth), + script: scripts?.script, + vars: fromOpenCollectionVariables(oc.request.variables), + tests: scripts?.tests + }; + } + + if (oc.docs) { + root.docs = typeof oc.docs === 'string' + ? oc.docs + : oc.docs.content || ''; + } + + root.meta = { + name: oc.info?.name || 'Untitled Collection' + }; + + return root; +}; + +export const openCollectionToBruno = (openCollection: OpenCollection): BrunoCollection => { + const brunoCollection: BrunoCollection = { + uid: uuid(), + name: openCollection.info?.name || 'Untitled Collection', + version: '1', + items: fromOpenCollectionItems(openCollection.items, (folder: unknown) => fromOpenCollectionFolder(folder as Parameters[0])), + environments: fromOpenCollectionEnvironments(openCollection.config?.environments), + brunoConfig: fromOpenCollectionConfig(openCollection) as Record, + root: fromOpenCollectionRoot(openCollection) + }; + + return brunoCollection; +}; \ No newline at end of file diff --git a/packages/bruno-converters/src/opencollection/types.ts b/packages/bruno-converters/src/opencollection/types.ts new file mode 100644 index 000000000..13ba07c0a --- /dev/null +++ b/packages/bruno-converters/src/opencollection/types.ts @@ -0,0 +1,208 @@ +// OpenCollection types - main module +export type { OpenCollection, Extensions } from '@opencollection/types'; + +// OpenCollection collection/item types +export type { Item, Folder, FolderInfo } from '@opencollection/types/collection/item'; + +// OpenCollection HTTP request types +export type { + HttpRequest, + HttpRequestHeader, + HttpResponseHeader, + HttpRequestParam, + HttpRequestBody, + HttpRequestSettings, + HttpRequestExample, + HttpRequestExampleRequest, + HttpRequestExampleResponse, + HttpRequestExampleResponseBody, + HttpRequestBodyVariant, + HttpRequestInfo, + HttpRequestDetails, + HttpRequestRuntime, + RawBody, + FormUrlEncodedBody, + FormUrlEncodedEntry, + MultipartFormBody, + MultipartFormEntry, + FileBody, + FileBodyVariant +} from '@opencollection/types/requests/http'; + +// OpenCollection GraphQL request types +export type { + GraphQLRequest, + GraphQLRequestSettings, + GraphQLRequestInfo, + GraphQLRequestDetails, + GraphQLRequestRuntime, + GraphQLBody, + GraphQLBodyVariant +} from '@opencollection/types/requests/graphql'; + +// OpenCollection gRPC request types +export type { + GrpcRequest, + GrpcRequestInfo, + GrpcRequestDetails, + GrpcRequestRuntime, + GrpcMetadata, + GrpcMessage, + GrpcMessageVariant, + GrpcMessagePayload, + GrpcMethodType +} from '@opencollection/types/requests/grpc'; + +// OpenCollection WebSocket request types +export type { + WebSocketRequest, + WebSocketRequestInfo, + WebSocketRequestDetails, + WebSocketRequestRuntime, + WebSocketMessage, + WebSocketMessageVariant, + WebSocketPayload, + WebSocketMessageType +} from '@opencollection/types/requests/websocket'; + +// OpenCollection config types +export type { Environment, CollectionConfig } from '@opencollection/types/config/environments'; +export type { Protobuf, ProtoFileItem, ProtoFileImportPath } from '@opencollection/types/config/protobuf'; +export type { Proxy, ProxyAuth } from '@opencollection/types/config/proxy'; +export type { ClientCertificate, PemCertificate, Pkcs12Certificate } from '@opencollection/types/config/certificates'; + +// OpenCollection common types +export type { RequestDefaults, RequestSettings } from '@opencollection/types/common/request-defaults'; +export type { Documentation } from '@opencollection/types/common/documentation'; +export type { Description } from '@opencollection/types/common/description'; +export type { Info, Author } from '@opencollection/types/common/info'; +export type { + Variable, + VariableValueVariant +} from '@opencollection/types/common/variables'; +export type { Scripts } from '@opencollection/types/common/scripts'; +export type { Assertion } from '@opencollection/types/common/assertions'; +export type { Tag } from '@opencollection/types/common/tags'; +export type { + Action, + ActionSetVariable, + ActionPhase, + ActionVariableScope, + SetVariableActionSelector, + SetVariableActionTarget +} from '@opencollection/types/common/actions'; + +// OpenCollection auth types +export type { + Auth, + AuthBasic, + AuthBearer, + AuthDigest, + AuthNTLM, + AuthAwsV4, + AuthApiKey, + AuthWsse +} from '@opencollection/types/common/auth'; + +export type { AuthOAuth2 } from '@opencollection/types/common/auth-oauth2'; + +// Bruno types - collection +export type { Item as BrunoItem } from '@usebruno/schema-types/collection/item'; +export type { + FolderRoot as BrunoFolderRoot, + FolderRequest as BrunoFolderRequest, + FolderMeta as BrunoFolderMeta +} from '@usebruno/schema-types/collection/folder'; +export type { Collection as BrunoCollection } from '@usebruno/schema-types/collection/collection'; +export type { + Environment as BrunoEnvironment, + EnvironmentVariable as BrunoEnvironmentVariable +} from '@usebruno/schema-types/collection/environment'; +export type { + Example as BrunoExample, + ExampleRequest as BrunoExampleRequest, + ExampleResponse as BrunoExampleResponse, + ExampleResponseBody as BrunoExampleResponseBody +} from '@usebruno/schema-types/collection/examples'; + +// Bruno types - common +export type { KeyValue as BrunoKeyValue } from '@usebruno/schema-types/common/key-value'; +export type { Variable as BrunoVariable, Variables as BrunoVariables } from '@usebruno/schema-types/common/variables'; +export type { Script as BrunoScript } from '@usebruno/schema-types/common/scripts'; +export type { + Auth as BrunoAuth, + AuthMode as BrunoAuthMode, + AuthAwsV4 as BrunoAuthAwsV4, + AuthBasic as BrunoAuthBasic, + AuthBearer as BrunoAuthBearer, + AuthDigest as BrunoAuthDigest, + AuthNTLM as BrunoAuthNTLM, + AuthWsse as BrunoAuthWsse, + AuthApiKey as BrunoAuthApiKey, + OAuth2 as BrunoOAuth2 +} from '@usebruno/schema-types/common/auth'; +export type { MultipartFormEntry as BrunoMultipartFormEntry, MultipartForm as BrunoMultipartForm } from '@usebruno/schema-types/common/multipart-form'; +export type { FileEntry as BrunoFileEntry, FileList as BrunoFileList } from '@usebruno/schema-types/common/file'; +export type { GraphqlBody as BrunoGraphqlBody } from '@usebruno/schema-types/common/graphql'; + +// Bruno types - requests +export type { + HttpRequest as BrunoHttpRequest, + HttpRequestBody as BrunoHttpRequestBody, + HttpRequestBodyMode as BrunoHttpRequestBodyMode, + HttpRequestParam as BrunoHttpRequestParam, + HttpRequestParamType as BrunoHttpRequestParamType +} from '@usebruno/schema-types/requests/http'; +export type { + GrpcRequest as BrunoGrpcRequest, + GrpcRequestBody as BrunoGrpcRequestBody, + GrpcMessage as BrunoGrpcMessage, + GrpcMethodType as BrunoGrpcMethodType +} from '@usebruno/schema-types/requests/grpc'; +export type { + WebSocketRequest as BrunoWebSocketRequest, + WebSocketRequestBody as BrunoWebSocketRequestBody, + WebSocketMessage as BrunoWsMessage +} from '@usebruno/schema-types/requests/websocket'; + +export interface BrunoConfig { + version?: string; + name?: string; + type?: string; + ignore?: string[]; + protobuf?: { + protoFiles?: { path: string }[]; + importPaths?: { path: string; disabled?: boolean }[]; + }; + proxy?: { + enabled?: boolean | 'global'; + protocol?: string; + hostname?: string; + port?: number; + auth?: { + enabled?: boolean; + username?: string; + password?: string; + }; + bypassProxy?: string; + }; + clientCertificates?: { + certs?: Array<{ + domain?: string; + type?: 'pem' | 'pkcs12'; + certFilePath?: string; + keyFilePath?: string; + pfxFilePath?: string; + passphrase?: string; + }>; + }; +} + +export interface BrunoCollectionRoot { + request?: any; + docs?: string; + meta?: { + name?: string; + seq?: number; + }; +} \ No newline at end of file diff --git a/packages/bruno-converters/src/wsdl/wsdl-to-bruno.js b/packages/bruno-converters/src/wsdl/wsdl-to-bruno.js index 659bebfc4..ca9374590 100644 --- a/packages/bruno-converters/src/wsdl/wsdl-to-bruno.js +++ b/packages/bruno-converters/src/wsdl/wsdl-to-bruno.js @@ -8,7 +8,7 @@ const generateUID = () => { return result; }; -import { get, each, filter } from 'lodash'; +import { get, each } from 'lodash'; import { collectionSchema } from '@usebruno/schema'; // --- Inlined from src/common/index.js --- diff --git a/packages/bruno-converters/tsconfig.json b/packages/bruno-converters/tsconfig.json index 57a8bcc74..f431df4f3 100644 --- a/packages/bruno-converters/tsconfig.json +++ b/packages/bruno-converters/tsconfig.json @@ -1,19 +1,31 @@ { "compilerOptions": { - "target": "ES6", - "esModuleInterop": true, - "strict": true, - "skipLibCheck": true, - "jsx": "react", + "target": "ES2020", "module": "ESNext", - "declaration": true, - "declarationDir": "types", - "sourceMap": true, - "outDir": "dist", - "moduleResolution": "node", - "emitDeclarationOnly": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true, "allowSyntheticDefaultImports": true, - "forceConsistentCasingInFileNames": true + "moduleResolution": "node", + "declaration": true, + "declarationMap": true, + "allowJs": true, + "checkJs": false, + "types": ["node"], + "lib": ["ES2020"], + "typeRoots": ["../../node_modules/@types", "./node_modules/@types"], + "baseUrl": "../..", + "paths": { + "@usebruno/schema-types": ["packages/bruno-schema-types/dist/index.d.ts"], + "@usebruno/schema-types/*": ["packages/bruno-schema-types/dist/*"], + "@opencollection/types": ["node_modules/@opencollection/types/dist/opencollection.d.ts"], + "@opencollection/types/*": ["node_modules/@opencollection/types/dist/*"] + } }, - "exclude": ["dist", "node_modules", "tests"] + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js"], + "exclude": ["node_modules", "dist"] }