diff --git a/package-lock.json b/package-lock.json index e7e658cb..373d8357 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,18 +20,18 @@ ], "devDependencies": { "@tauri-apps/cli": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.18.1", + "@typescript-eslint/parser": "^8.18.1", "eslint": "^8", "eslint-config-prettier": "^8", - "eslint-plugin-import": "^2.30.0", - "eslint-plugin-jsx-a11y": "^6.10.0", - "eslint-plugin-react": "^7.35.2", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0", "nodejs-file-downloader": "^4.13.0", "npm-run-all": "^4.1.5", - "prettier": "^3.3.3", - "typescript": "^5.6.2" + "prettier": "^3.4.2", + "typescript": "^5.7.2" } }, "node_modules/@alloc/quick-lru": { @@ -3399,17 +3399,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.8.1.tgz", - "integrity": "sha512-xfvdgA8AP/vxHgtgU310+WBnLB4uJQ9XdyP17RebG26rLtDrQJV3ZYrcopX91GrHmMoH8bdSwMRh2a//TiJ1jQ==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", + "integrity": "sha512-Ncvsq5CT3Gvh+uJG0Lwlho6suwDfUXH0HztslDf5I+F2wAFAZMRwYLEorumpKLzmO2suAXZ/td1tBg4NZIi9CQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/type-utils": "8.8.1", - "@typescript-eslint/utils": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/type-utils": "8.18.1", + "@typescript-eslint/utils": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -3424,25 +3424,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.8.1.tgz", - "integrity": "sha512-hQUVn2Lij2NAxVFEdvIGxT9gP1tq2yM83m+by3whWFsWC+1y8pxxxHUFE1UqDu2VsGi2i6RLcv4QvouM84U+ow==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.1.tgz", + "integrity": "sha512-rBnTWHCdbYM2lh7hjyXqxk70wvon3p2FyaniZuey5TrcGBpfhVp0OxOa6gxr9Q9YhZFKyfbEnxc24ZnVbbUkCA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4" }, "engines": { @@ -3453,23 +3449,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.8.1.tgz", - "integrity": "sha512-X4JdU+66Mazev/J0gfXlcC/dV6JI37h+93W9BRYXrSn0hrE64IoWgVkO9MSJgEzoWkxONgaQpICWg8vAN74wlA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.1.tgz", + "integrity": "sha512-HxfHo2b090M5s2+/9Z3gkBhI6xBH8OJCFjH9MhQ+nnoZqxU3wNxkLT+VWXWSFWc3UF3Z+CfPAyqdCTdoXtDPCQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1" + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3480,14 +3472,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.8.1.tgz", - "integrity": "sha512-qSVnpcbLP8CALORf0za+vjLYj1Wp8HSoiI8zYU5tHxRVj30702Z1Yw4cLwfNKhTPWp5+P+k1pjmD5Zd1nhxiZA==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.1.tgz", + "integrity": "sha512-jAhTdK/Qx2NJPNOTxXpMwlOiSymtR2j283TtPqXkKBdH8OAMmhiUfP0kJjc/qSE51Xrq02Gj9NY7MwK+UxVwHQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.8.1", - "@typescript-eslint/utils": "8.8.1", + "@typescript-eslint/typescript-estree": "8.18.1", + "@typescript-eslint/utils": "8.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -3498,16 +3490,15 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.8.1.tgz", - "integrity": "sha512-WCcTP4SDXzMd23N27u66zTKMuEevH4uzU8C9jf0RO4E04yVHgQgW+r+TeVTNnO1KIfrL8ebgVVYYMMO3+jC55Q==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.1.tgz", + "integrity": "sha512-7uoAUsCj66qdNQNpH2G8MyTFlgerum8ubf21s3TSM3XmKXuIn+H2Sifh/ES2nPOPiYSRJWAk0fDkW0APBWcpfw==", "dev": true, "license": "MIT", "engines": { @@ -3519,14 +3510,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.8.1.tgz", - "integrity": "sha512-A5d1R9p+X+1js4JogdNilDuuq+EHZdsH9MjTVxXOdVFfTJXunKJR/v+fNNyO4TnoOn5HqobzfRlc70NC6HTcdg==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.1.tgz", + "integrity": "sha512-z8U21WI5txzl2XYOW7i9hJhxoKKNG1kcU4RzyNvKrdZDmbjkmLBo8bgeiOJmA06kizLI76/CCBAAGlTlEeUfyg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/visitor-keys": "8.8.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/visitor-keys": "8.18.1", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3541,23 +3532,21 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.8.1.tgz", - "integrity": "sha512-/QkNJDbV0bdL7H7d0/y0qBbV2HTtf0TIyjSDTvvmQEzeVx8jEImEbLuOA4EsvE8gIgqMitns0ifb5uQhMj8d9w==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.1.tgz", + "integrity": "sha512-8vikiIj2ebrC4WRdcAdDcmnu9Q/MXXwg+STf40BVfT8exDqBCUPdypvzcUPxEqRGKg9ALagZ0UWcYCtn+4W2iQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.8.1", - "@typescript-eslint/types": "8.8.1", - "@typescript-eslint/typescript-estree": "8.8.1" + "@typescript-eslint/scope-manager": "8.18.1", + "@typescript-eslint/types": "8.18.1", + "@typescript-eslint/typescript-estree": "8.18.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3567,18 +3556,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.8.1.tgz", - "integrity": "sha512-0/TdC3aeRAsW7MDvYRwEc1Uwm0TIBfzjPFgg60UU2Haj5qsCs9cc3zNgY71edqE3LbWfF/WoZQd3lJoDXFQpag==", + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.1.tgz", + "integrity": "sha512-Vj0WLm5/ZsD013YeUKn+K0y8p1M0jPpxOkKdbD1wB0ns53a5piVY02zjf072TblEweAbcYiFiPoSMF3kp+VhhQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.8.1", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.18.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3588,6 +3578,19 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -3871,13 +3874,13 @@ "license": "Python-2.0" }, "node_modules/aria-query": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.1.3.tgz", - "integrity": "sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", - "dependencies": { - "deep-equal": "^2.0.5" + "engines": { + "node": ">= 0.4" } }, "node_modules/array-buffer-byte-length": { @@ -5604,39 +5607,6 @@ "node": ">=0.10.0" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -6087,27 +6057,6 @@ "node": ">= 0.4" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-iterator-helpers": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.1.0.tgz", @@ -6471,13 +6420,13 @@ } }, "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.10.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz", - "integrity": "sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==", + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", "dev": true, "license": "MIT", "dependencies": { - "aria-query": "~5.1.3", + "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", @@ -6485,14 +6434,13 @@ "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", - "es-iterator-helpers": "^1.0.19", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", - "string.prototype.includes": "^2.0.0" + "string.prototype.includes": "^2.0.1" }, "engines": { "node": ">=4.0" @@ -6526,9 +6474,9 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.1.tgz", - "integrity": "sha512-xwTnwDqzbDRA8uJ7BMxPs/EXRB3i8ZfnOIp8BsxEQkT0nHPp+WWceqGgo6rKb9ctNi8GJLDT4Go5HAWELa/WMg==", + "version": "7.37.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", + "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", "dev": true, "license": "MIT", "dependencies": { @@ -6537,7 +6485,7 @@ "array.prototype.flatmap": "^1.3.2", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.0.19", + "es-iterator-helpers": "^1.1.0", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", @@ -6559,16 +6507,26 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz", + "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz", + "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" } }, "node_modules/eslint-plugin-react-refresh": { @@ -8120,23 +8078,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-arguments": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", - "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -10818,23 +10759,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/object-is": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", - "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -13277,19 +13201,6 @@ "stacktrace-gps": "^3.0.4" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", - "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "internal-slot": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -13349,14 +13260,18 @@ "license": "MIT" }, "node_modules/string.prototype.includes": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.0.tgz", - "integrity": "sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" } }, "node_modules/string.prototype.matchall": { @@ -14774,9 +14689,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -15891,7 +15806,7 @@ }, "plugin-runtime-types": { "name": "@yaakapp/api", - "version": "0.2.16", + "version": "0.2.17", "dependencies": { "@types/node": "^22.5.4" }, diff --git a/package.json b/package.json index 2d950bf1..a51ae20e 100644 --- a/package.json +++ b/package.json @@ -33,17 +33,17 @@ }, "devDependencies": { "@tauri-apps/cli": "^2.1.0", - "@typescript-eslint/eslint-plugin": "^8.5.0", - "@typescript-eslint/parser": "^8.5.0", + "@typescript-eslint/eslint-plugin": "^8.18.1", + "@typescript-eslint/parser": "^8.18.1", "eslint": "^8", "eslint-config-prettier": "^8", - "eslint-plugin-import": "^2.30.0", - "eslint-plugin-jsx-a11y": "^6.10.0", - "eslint-plugin-react": "^7.35.2", - "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.2", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.1.0", "nodejs-file-downloader": "^4.13.0", "npm-run-all": "^4.1.5", - "prettier": "^3.3.3", - "typescript": "^5.6.2" + "prettier": "^3.4.2", + "typescript": "^5.7.2" } } diff --git a/plugin-runtime-types/package.json b/plugin-runtime-types/package.json index dc8adedd..a03d75ed 100644 --- a/plugin-runtime-types/package.json +++ b/plugin-runtime-types/package.json @@ -1,6 +1,6 @@ { "name": "@yaakapp/api", - "version": "0.2.16", + "version": "0.2.17", "main": "lib/index.js", "typings": "./lib/index.d.ts", "files": [ diff --git a/plugin-runtime-types/src/bindings/models.ts b/plugin-runtime-types/src/bindings/models.ts index 831608d5..0ea9b8f7 100644 --- a/plugin-runtime-types/src/bindings/models.ts +++ b/plugin-runtime-types/src/bindings/models.ts @@ -1,16 +1,16 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, variables: Array, }; +export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, }; -export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, sortPriority: number, }; +export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, }; export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, }; -export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; +export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, }; @@ -22,4 +22,4 @@ export type HttpResponseState = "initialized" | "connected" | "closed"; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, }; -export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; +export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; diff --git a/src-tauri/migrations/20241219140051_base-environments.sql b/src-tauri/migrations/20241219140051_base-environments.sql new file mode 100644 index 00000000..d96ae8f5 --- /dev/null +++ b/src-tauri/migrations/20241219140051_base-environments.sql @@ -0,0 +1,45 @@ +-- Add the new field +ALTER TABLE environments + ADD COLUMN environment_id TEXT REFERENCES environments (id) ON DELETE CASCADE; + +-- Create temporary column so we know which rows are meant to be base environments. We'll use this to update +-- child environments to point to them. +ALTER TABLE environments + ADD COLUMN migrated_base_env BOOLEAN DEFAULT FALSE NOT NULL; + +-- Create a base environment for each workspace +INSERT INTO environments (id, workspace_id, name, variables, migrated_base_env) +SELECT ( + -- This is the best way to generate a random string in SQLite, apparently + 'ev_' || SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) || + SUBSTR('abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23457789', (ABS(RANDOM()) % 57) + 1, 1) + ), + workspaces.id, + 'Global Variables', + variables, + TRUE +FROM workspaces; + +-- Update all non-base environments to point to newly created base environments +UPDATE environments +SET environment_id = ( SELECT base_env.id + FROM environments AS base_env + WHERE base_env.workspace_id = environments.workspace_id + AND base_env.migrated_base_env IS TRUE ) +WHERE migrated_base_env IS FALSE; + +-- Drop temporary column +ALTER TABLE environments + DROP COLUMN migrated_base_env; + +-- Drop the old variables column +-- IMPORTANT: Skip to give the user the option to roll back to a previous app version. We can drop it once the migration working in the real world +-- ALTER TABLE workspaces DROP COLUMN variables; diff --git a/src-tauri/src/export_resources.rs b/src-tauri/src/export_resources.rs index 7179715d..61443508 100644 --- a/src-tauri/src/export_resources.rs +++ b/src-tauri/src/export_resources.rs @@ -34,7 +34,7 @@ pub async fn get_workspace_export_resources( let app_handle = window.app_handle(); let mut data = WorkspaceExport { yaak_version: app_handle.package_info().version.clone().to_string(), - yaak_schema: 2, + yaak_schema: 3, timestamp: chrono::Utc::now().naive_utc(), resources: WorkspaceExportResources { workspaces: Vec::new(), diff --git a/src-tauri/src/http_request.rs b/src-tauri/src/http_request.rs index 0c7ed354..ed2ef075 100644 --- a/src-tauri/src/http_request.rs +++ b/src-tauri/src/http_request.rs @@ -28,8 +28,8 @@ use yaak_models::models::{ HttpResponseState, ProxySetting, ProxySettingAuth, }; use yaak_models::queries::{ - get_http_response, get_or_create_settings, get_workspace, update_response_if_id, - upsert_cookie_jar, + get_base_environment, get_http_response, get_or_create_settings, get_workspace, + update_response_if_id, upsert_cookie_jar, }; use yaak_plugin_runtime::events::{RenderPurpose, WindowContext}; @@ -43,6 +43,9 @@ pub async fn send_http_request( ) -> Result { let workspace = get_workspace(window, &request.workspace_id).await.expect("Failed to get Workspace"); + let base_environment = get_base_environment(window, &request.workspace_id) + .await + .expect("Failed to get base environment"); let settings = get_or_create_settings(window).await; let cb = PluginTemplateCallback::new( window.app_handle(), @@ -54,7 +57,7 @@ pub async fn send_http_request( let response = Arc::new(Mutex::new(og_response.clone())); let rendered_request = - render_http_request(&request, &workspace, environment.as_ref(), &cb).await; + render_http_request(&request, &base_environment, environment.as_ref(), &cb).await; let mut url_string = rendered_request.url; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 5bb87811..479ee0f7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -56,14 +56,15 @@ use yaak_models::queries::{ delete_cookie_jar, delete_environment, delete_folder, delete_grpc_connection, delete_grpc_request, delete_http_request, delete_http_response, delete_plugin, delete_workspace, duplicate_folder, duplicate_grpc_request, duplicate_http_request, - generate_id, generate_model_id, get_cookie_jar, get_environment, get_folder, - get_grpc_connection, get_grpc_request, get_http_request, get_http_response, get_key_value_raw, - get_or_create_settings, get_plugin, get_workspace, list_cookie_jars, list_environments, - list_folders, list_grpc_connections_for_workspace, list_grpc_events, list_grpc_requests, - list_http_requests, list_http_responses_for_request, list_http_responses_for_workspace, - list_plugins, list_workspaces, set_key_value_raw, update_response_if_id, update_settings, - upsert_cookie_jar, upsert_environment, upsert_folder, upsert_grpc_connection, - upsert_grpc_event, upsert_grpc_request, upsert_http_request, upsert_plugin, upsert_workspace, + generate_id, generate_model_id, get_base_environment, get_cookie_jar, get_environment, + get_folder, get_grpc_connection, get_grpc_request, get_http_request, get_http_response, + get_key_value_raw, get_or_create_settings, get_plugin, get_workspace, list_cookie_jars, + list_environments, list_folders, list_grpc_connections_for_workspace, list_grpc_events, + list_grpc_requests, list_http_requests, list_http_responses_for_request, + list_http_responses_for_workspace, list_plugins, list_workspaces, set_key_value_raw, + update_response_if_id, update_settings, upsert_cookie_jar, upsert_environment, upsert_folder, + upsert_grpc_connection, upsert_grpc_event, upsert_grpc_request, upsert_http_request, + upsert_plugin, upsert_workspace, }; use yaak_plugin_runtime::events::{ BootResponse, CallHttpRequestActionRequest, FilterResponse, FindHttpResponsesResponse, @@ -143,10 +144,11 @@ async fn cmd_render_template( Some(id) => Some(get_environment(&window, id).await.map_err(|e| e.to_string())?), None => None, }; - let workspace = get_workspace(&window, &workspace_id).await.map_err(|e| e.to_string())?; + let base_environment = + get_base_environment(&window, &workspace_id).await.map_err(|e| e.to_string())?; let rendered = render_template( template, - &workspace, + &base_environment, environment.as_ref(), &PluginTemplateCallback::new( &app_handle, @@ -208,10 +210,11 @@ async fn cmd_grpc_go( .await .map_err(|e| e.to_string())? .ok_or("Failed to find GRPC request")?; - let workspace = get_workspace(&window, &req.workspace_id).await.map_err(|e| e.to_string())?; + let base_environment = + get_base_environment(&window, &req.workspace_id).await.map_err(|e| e.to_string())?; let req = render_grpc_request( &req, - &workspace, + &base_environment, environment.as_ref(), &PluginTemplateCallback::new( window.app_handle(), @@ -337,7 +340,7 @@ async fn cmd_grpc_go( let cb = { let cancelled_rx = cancelled_rx.clone(); let window = window.clone(); - let workspace = workspace.clone(); + let workspace = base_environment.clone(); let environment = environment.clone(); let base_msg = base_msg.clone(); let method_desc = method_desc.clone(); @@ -433,7 +436,7 @@ async fn cmd_grpc_go( let msg = if req.message.is_empty() { "{}".to_string() } else { req.message }; let msg = render_template( msg.as_str(), - &workspace.clone(), + &base_environment.clone(), environment.as_ref(), &PluginTemplateCallback::new( window.app_handle(), @@ -852,12 +855,31 @@ async fn cmd_import_data( } info!("Imported {} workspaces", imported_resources.workspaces.len()); - for mut v in resources.environments { - v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map); - v.workspace_id = - maybe_gen_id(v.workspace_id.as_str(), ModelType::TypeWorkspace, &mut id_map); - let x = upsert_environment(&window, v).await.map_err(|e| e.to_string())?; - imported_resources.environments.push(x.clone()); + // Environments can foreign-key to themselves, so we need to import from + // the top of the tree to the bottom to avoid foreign key conflicts. + // We do this by looping until we've imported them all, only importing if: + // - The parent has been imported + // - The environment hasn't already been imported + // The loop exits when imported.len == to_import.len + while imported_resources.environments.len() < resources.environments.len() { + for mut v in resources.environments.clone() { + v.id = maybe_gen_id(v.id.as_str(), ModelType::TypeEnvironment, &mut id_map); + v.workspace_id = + maybe_gen_id(v.workspace_id.as_str(), ModelType::TypeWorkspace, &mut id_map); + v.environment_id = + maybe_gen_id_opt(v.environment_id, ModelType::TypeEnvironment, &mut id_map); + if let Some(fid) = v.environment_id.clone() { + let imported_parent = imported_resources.environments.iter().find(|f| f.id == fid); + if imported_parent.is_none() { + continue; + } + } + if let Some(_) = imported_resources.environments.iter().find(|f| f.id == v.id) { + continue; + } + let x = upsert_environment(&window, v).await.map_err(|e| e.to_string())?; + imported_resources.environments.push(x.clone()); + } } info!("Imported {} environments", imported_resources.environments.len()); @@ -2109,9 +2131,17 @@ async fn handle_plugin_event( .await .expect("Failed to get workspace_id from window URL"); let environment = environment_from_window(&window).await; + let base_environment = get_base_environment(&window, workspace.id.as_str()) + .await + .expect("Failed to get base environment"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); - let http_request = - render_http_request(&req.http_request, &workspace, environment.as_ref(), &cb).await; + let http_request = render_http_request( + &req.http_request, + &base_environment, + environment.as_ref(), + &cb, + ) + .await; Some(InternalEventPayload::RenderHttpRequestResponse(RenderHttpRequestResponse { http_request, })) @@ -2124,8 +2154,12 @@ async fn handle_plugin_event( .await .expect("Failed to get workspace_id from window URL"); let environment = environment_from_window(&window).await; + let base_environment = get_base_environment(&window, workspace.id.as_str()) + .await + .expect("Failed to get base environment"); let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); - let data = render_json_value(req.data, &workspace, environment.as_ref(), &cb).await; + let data = + render_json_value(req.data, &base_environment, environment.as_ref(), &cb).await; Some(InternalEventPayload::TemplateRenderResponse(TemplateRenderResponse { data })) } InternalEventPayload::ReloadResponse => { diff --git a/src-tauri/src/render.rs b/src-tauri/src/render.rs index 6002aa07..c815cdf4 100644 --- a/src-tauri/src/render.rs +++ b/src-tauri/src/render.rs @@ -3,37 +3,37 @@ use serde_json::{json, Map, Value}; use std::collections::{BTreeMap, HashMap}; use yaak_models::models::{ Environment, EnvironmentVariable, GrpcMetadataEntry, GrpcRequest, HttpRequest, - HttpRequestHeader, HttpUrlParameter, Workspace, + HttpRequestHeader, HttpUrlParameter, }; use yaak_templates::{parse_and_render, TemplateCallback}; pub async fn render_template( template: &str, - w: &Workspace, - e: Option<&Environment>, + base_environment: &Environment, + environment: Option<&Environment>, cb: &T, ) -> String { - let vars = &make_vars_hashmap(w, e); + let vars = &make_vars_hashmap(base_environment, environment); render(template, vars, cb).await } pub async fn render_json_value( value: Value, - w: &Workspace, - e: Option<&Environment>, + base_environment: &Environment, + environment: Option<&Environment>, cb: &T, ) -> Value { - let vars = &make_vars_hashmap(w, e); + let vars = &make_vars_hashmap(base_environment, environment); render_json_value_raw(value, vars, cb).await } pub async fn render_grpc_request( r: &GrpcRequest, - w: &Workspace, - e: Option<&Environment>, + base_environment: &Environment, + environment: Option<&Environment>, cb: &T, ) -> GrpcRequest { - let vars = &make_vars_hashmap(w, e); + let vars = &make_vars_hashmap(base_environment, environment); let mut metadata = Vec::new(); for p in r.metadata.clone() { @@ -61,11 +61,11 @@ pub async fn render_grpc_request( pub async fn render_http_request( r: &HttpRequest, - w: &Workspace, - e: Option<&Environment>, + base_environment: &Environment, + environment: Option<&Environment>, cb: &PluginTemplateCallback, ) -> HttpRequest { - let vars = &make_vars_hashmap(w, e); + let vars = &make_vars_hashmap(base_environment, environment); let mut url_parameters = Vec::new(); for p in r.url_parameters.clone() { @@ -110,11 +110,11 @@ pub async fn render_http_request( } pub fn make_vars_hashmap( - workspace: &Workspace, + base_environment: &Environment, environment: Option<&Environment>, ) -> HashMap { let mut variables = HashMap::new(); - variables = add_variable_to_map(variables, &workspace.variables); + variables = add_variable_to_map(variables, &base_environment.variables); if let Some(e) = environment { variables = add_variable_to_map(variables, &e.variables); diff --git a/src-tauri/vendored/plugins/importer-insomnia/build/index.js b/src-tauri/vendored/plugins/importer-insomnia/build/index.js index 7608a3f7..e89c90db 100644 --- a/src-tauri/vendored/plugins/importer-insomnia/build/index.js +++ b/src-tauri/vendored/plugins/importer-insomnia/build/index.js @@ -7233,19 +7233,15 @@ function pluginHookImport(ctx, contents) { }; const workspacesToImport = parsed.resources.filter(isWorkspace); for (const workspaceToImport of workspacesToImport) { - const baseEnvironment = parsed.resources.find( - (r) => isEnvironment(r) && r.parentId === workspaceToImport._id - ); resources.workspaces.push({ id: convertId(workspaceToImport._id), createdAt: new Date(workspacesToImport.created ?? Date.now()).toISOString().replace("Z", ""), updatedAt: new Date(workspacesToImport.updated ?? Date.now()).toISOString().replace("Z", ""), model: "workspace", - name: workspaceToImport.name, - variables: baseEnvironment ? parseVariables(baseEnvironment.data) : [] + name: workspaceToImport.name }); const environmentsToImport = parsed.resources.filter( - (r) => isEnvironment(r) && r.parentId === baseEnvironment?._id + (r) => isEnvironment(r) ); resources.environments.push( ...environmentsToImport.map((r) => importEnvironment(r, workspaceToImport._id)) @@ -7394,13 +7390,6 @@ function importHttpRequest(r, workspaceId, sortPriority = 0) { })).filter(({ name, value }) => name !== "" || value !== "") }; } -function parseVariables(data) { - return Object.entries(data).map(([name, value]) => ({ - enabled: true, - name, - value: `${value}` - })); -} function convertSyntax(variable) { if (!isJSString(variable)) return variable; return variable.replaceAll(/{{\s*(_\.)?([^}]+)\s*}}/g, "${[$2]}"); diff --git a/src-tauri/vendored/plugins/importer-openapi/build/index.js b/src-tauri/vendored/plugins/importer-openapi/build/index.js index bad1a7db..aad8421e 100644 --- a/src-tauri/vendored/plugins/importer-openapi/build/index.js +++ b/src-tauri/vendored/plugins/importer-openapi/build/index.js @@ -145878,13 +145878,20 @@ function pluginHookImport(_ctx, contents) { model: "workspace", id: generateId("workspace"), name: info.name || "Postman Import", - description: info.description?.content ?? info.description ?? "", + description: info.description?.content ?? info.description ?? "" + }; + exportResources.workspaces.push(workspace); + const environment = { + model: "environment", + id: generateId("environment"), + name: "Global Variables", + workspaceId: workspace.id, variables: root.variable?.map((v) => ({ name: v.key, value: v.value })) ?? [] }; - exportResources.workspaces.push(workspace); + exportResources.environments.push(environment); const importItem = (v, folderId = null) => { if (typeof v.name === "string" && Array.isArray(v.item)) { const folder = { diff --git a/src-tauri/vendored/plugins/importer-postman/build/index.js b/src-tauri/vendored/plugins/importer-postman/build/index.js index 9543e279..c3dd59a8 100644 --- a/src-tauri/vendored/plugins/importer-postman/build/index.js +++ b/src-tauri/vendored/plugins/importer-postman/build/index.js @@ -45,13 +45,20 @@ function pluginHookImport(_ctx, contents) { model: "workspace", id: generateId("workspace"), name: info.name || "Postman Import", - description: info.description?.content ?? info.description ?? "", + description: info.description?.content ?? info.description ?? "" + }; + exportResources.workspaces.push(workspace); + const environment = { + model: "environment", + id: generateId("environment"), + name: "Global Variables", + workspaceId: workspace.id, variables: root.variable?.map((v) => ({ name: v.key, value: v.value })) ?? [] }; - exportResources.workspaces.push(workspace); + exportResources.environments.push(environment); const importItem = (v, folderId = null) => { if (typeof v.name === "string" && Array.isArray(v.item)) { const folder = { diff --git a/src-tauri/vendored/plugins/importer-yaak/build/index.js b/src-tauri/vendored/plugins/importer-yaak/build/index.js index a02fa336..b82f77c7 100644 --- a/src-tauri/vendored/plugins/importer-yaak/build/index.js +++ b/src-tauri/vendored/plugins/importer-yaak/build/index.js @@ -41,6 +41,24 @@ function pluginHookImport(_ctx, contents) { parsed.resources.httpRequests = parsed.resources.requests; delete parsed.resources["requests"]; } + for (const workspace of parsed.resources.workspaces ?? []) { + if ("variables" in workspace) { + const baseEnvironment = { + id: `GENERATE_ID::base_env_${workspace["id"]}`, + name: "Global Variables", + variables: workspace.variables, + workspaceId: workspace.id + }; + parsed.resources.environments = parsed.resources.environments ?? []; + parsed.resources.environments.push(baseEnvironment); + delete workspace.variables; + for (const environment of parsed.resources.environments) { + if (environment.workspaceId === workspace.id && environment.id !== baseEnvironment.id) { + environment.environmentId = baseEnvironment.id; + } + } + } + } return { resources: parsed.resources }; } function isJSObject(obj) { diff --git a/src-tauri/yaak_models/bindings/models.ts b/src-tauri/yaak_models/bindings/models.ts index 3872c2ce..8b7343f6 100644 --- a/src-tauri/yaak_models/bindings/models.ts +++ b/src-tauri/yaak_models/bindings/models.ts @@ -10,7 +10,7 @@ export type CookieExpires = { "AtUtc": string } | "SessionEnd"; export type CookieJar = { model: "cookie_jar", id: string, createdAt: string, updatedAt: string, workspaceId: string, cookies: Array, name: string, }; -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, variables: Array, }; +export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, }; @@ -50,4 +50,4 @@ export type ProxySettingAuth = { user: string, password: string, }; export type Settings = { model: "settings", id: string, createdAt: string, updatedAt: string, appearance: string, editorFontSize: number, editorSoftWrap: boolean, interfaceFontSize: number, interfaceScale: number, openWorkspaceNewWindow: boolean | null, telemetry: boolean, theme: string, themeDark: string, themeLight: string, updateChannel: string, proxy: ProxySetting | null, }; -export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; +export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; diff --git a/src-tauri/yaak_models/src/models.rs b/src-tauri/yaak_models/src/models.rs index ac3ca348..9a759f63 100644 --- a/src-tauri/yaak_models/src/models.rs +++ b/src-tauri/yaak_models/src/models.rs @@ -110,7 +110,6 @@ pub struct Workspace { pub updated_at: NaiveDateTime, pub name: String, pub description: String, - pub variables: Vec, // Settings #[serde(default = "default_true")] @@ -134,14 +133,12 @@ pub enum WorkspaceIden { SettingFollowRedirects, SettingRequestTimeout, SettingValidateCertificates, - Variables, } impl<'s> TryFrom<&Row<'s>> for Workspace { type Error = rusqlite::Error; fn try_from(r: &Row<'s>) -> Result { - let variables: String = r.get("variables")?; Ok(Workspace { id: r.get("id")?, model: r.get("model")?, @@ -149,7 +146,6 @@ impl<'s> TryFrom<&Row<'s>> for Workspace { updated_at: r.get("updated_at")?, name: r.get("name")?, description: r.get("description")?, - variables: serde_json::from_str(variables.as_str()).unwrap_or_default(), setting_validate_certificates: r.get("setting_validate_certificates")?, setting_follow_redirects: r.get("setting_follow_redirects")?, setting_request_timeout: r.get("setting_request_timeout")?, @@ -248,6 +244,7 @@ pub struct Environment { pub model: String, pub id: String, pub workspace_id: String, + pub environment_id: Option, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, @@ -263,6 +260,7 @@ pub enum EnvironmentIden { Id, CreatedAt, UpdatedAt, + EnvironmentId, WorkspaceId, Name, @@ -278,6 +276,7 @@ impl<'s> TryFrom<&Row<'s>> for Environment { id: r.get("id")?, model: r.get("model")?, workspace_id: r.get("workspace_id")?, + environment_id: r.get("environment_id")?, created_at: r.get("created_at")?, updated_at: r.get("updated_at")?, name: r.get("name")?, diff --git a/src-tauri/yaak_models/src/queries.rs b/src-tauri/yaak_models/src/queries.rs index 75eb5006..51acd3db 100644 --- a/src-tauri/yaak_models/src/queries.rs +++ b/src-tauri/yaak_models/src/queries.rs @@ -10,7 +10,7 @@ use crate::models::{ Settings, SettingsIden, Workspace, WorkspaceIden, }; use crate::plugin::SqliteConnection; -use log::{debug, error}; +use log::{debug, error, info}; use rand::distributions::{Alphanumeric, DistString}; use rusqlite::OptionalExtension; use sea_query::ColumnRef::Asterisk; @@ -191,7 +191,6 @@ pub async fn upsert_workspace( WorkspaceIden::UpdatedAt, WorkspaceIden::Name, WorkspaceIden::Description, - WorkspaceIden::Variables, WorkspaceIden::SettingRequestTimeout, WorkspaceIden::SettingFollowRedirects, WorkspaceIden::SettingValidateCertificates, @@ -202,7 +201,6 @@ pub async fn upsert_workspace( CurrentTimestamp.into(), trimmed_name.into(), workspace.description.into(), - serde_json::to_string(&workspace.variables)?.into(), workspace.setting_request_timeout.into(), workspace.setting_follow_redirects.into(), workspace.setting_validate_certificates.into(), @@ -213,7 +211,6 @@ pub async fn upsert_workspace( WorkspaceIden::UpdatedAt, WorkspaceIden::Name, WorkspaceIden::Description, - WorkspaceIden::Variables, WorkspaceIden::SettingRequestTimeout, WorkspaceIden::SettingFollowRedirects, WorkspaceIden::SettingValidateCertificates, @@ -739,21 +736,41 @@ pub async fn upsert_cookie_jar( } pub async fn list_environments( - mgr: &impl Manager, + window: &WebviewWindow, workspace_id: &str, ) -> Result> { - let dbm = &*mgr.state::(); - let db = dbm.0.lock().await.get().unwrap(); + let mut environments: Vec = { + let dbm = &*window.state::(); + let db = dbm.0.lock().await.get().unwrap(); + let (sql, params) = Query::select() + .from(EnvironmentIden::Table) + .cond_where(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) + .column(Asterisk) + .order_by(EnvironmentIden::CreatedAt, Order::Desc) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = db.prepare(sql.as_str())?; + let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; + items.map(|v| v.unwrap()).collect() + }; - let (sql, params) = Query::select() - .from(EnvironmentIden::Table) - .cond_where(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) - .column(Asterisk) - .order_by(EnvironmentIden::CreatedAt, Order::Desc) - .build_rusqlite(SqliteQueryBuilder); - let mut stmt = db.prepare(sql.as_str())?; - let items = stmt.query_map(&*params.as_params(), |row| row.try_into())?; - Ok(items.map(|v| v.unwrap()).collect()) + let base_environment = + environments.iter().find(|e| e.environment_id == None && e.workspace_id == workspace_id); + + if let None = base_environment { + let base_environment = upsert_environment( + window, + Environment { + workspace_id: workspace_id.to_string(), + name: "Global Variables".to_string(), + ..Default::default() + }, + ) + .await?; + info!("Created base environment for {workspace_id}"); + environments.push(base_environment); + } + + Ok(environments) } pub async fn delete_environment( @@ -839,7 +856,7 @@ pub async fn update_settings( None => None, Some(p) => Some(serde_json::to_string(&p)?), }) - .into(), + .into(), ), ]) .returning_all() @@ -869,6 +886,7 @@ pub async fn upsert_environment( EnvironmentIden::Id, EnvironmentIden::CreatedAt, EnvironmentIden::UpdatedAt, + EnvironmentIden::EnvironmentId, EnvironmentIden::WorkspaceId, EnvironmentIden::Name, EnvironmentIden::Variables, @@ -877,7 +895,8 @@ pub async fn upsert_environment( id.as_str().into(), CurrentTimestamp.into(), CurrentTimestamp.into(), - environment.workspace_id.as_str().into(), + environment.environment_id.into(), + environment.workspace_id.into(), trimmed_name.into(), serde_json::to_string(&environment.variables)?.into(), ]) @@ -911,6 +930,26 @@ pub async fn get_environment(mgr: &impl Manager, id: &str) -> Res Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) } +pub async fn get_base_environment( + mgr: &impl Manager, + workspace_id: &str, +) -> Result { + let dbm = &*mgr.state::(); + let db = dbm.0.lock().await.get().unwrap(); + + let (sql, params) = Query::select() + .from(EnvironmentIden::Table) + .column(Asterisk) + .cond_where( + Cond::all() + .add(Expr::col(EnvironmentIden::WorkspaceId).eq(workspace_id)) + .add(Expr::col(EnvironmentIden::EnvironmentId).is_null()), + ) + .build_rusqlite(SqliteQueryBuilder); + let mut stmt = db.prepare(sql.as_str())?; + Ok(stmt.query_row(&*params.as_params(), |row| row.try_into())?) +} + pub async fn get_plugin(mgr: &impl Manager, id: &str) -> Result { let dbm = &*mgr.state::(); let db = dbm.0.lock().await.get().unwrap(); @@ -1142,7 +1181,7 @@ pub async fn duplicate_folder( ..src_folder.clone() }, ) - .await?; + .await?; for m in http_requests { upsert_http_request( @@ -1154,7 +1193,7 @@ pub async fn duplicate_folder( ..m }, ) - .await?; + .await?; } for m in grpc_requests { upsert_grpc_request( @@ -1166,7 +1205,7 @@ pub async fn duplicate_folder( ..m }, ) - .await?; + .await?; } for m in folders { // Recurse down @@ -1177,7 +1216,7 @@ pub async fn duplicate_folder( ..m }, )) - .await?; + .await?; } Ok(()) } @@ -1336,7 +1375,7 @@ pub async fn create_default_http_response( None, None, ) - .await + .await } #[allow(clippy::too_many_arguments)] diff --git a/src-tauri/yaak_plugin_runtime/bindings/models.ts b/src-tauri/yaak_plugin_runtime/bindings/models.ts index 831608d5..0ea9b8f7 100644 --- a/src-tauri/yaak_plugin_runtime/bindings/models.ts +++ b/src-tauri/yaak_plugin_runtime/bindings/models.ts @@ -1,16 +1,16 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type Environment = { model: "environment", id: string, workspaceId: string, createdAt: string, updatedAt: string, name: string, variables: Array, }; +export type Environment = { model: "environment", id: string, workspaceId: string, environmentId: string | null, createdAt: string, updatedAt: string, name: string, variables: Array, }; export type EnvironmentVariable = { enabled?: boolean, name: string, value: string, }; -export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, sortPriority: number, }; +export type Folder = { model: "folder", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, name: string, description: string, sortPriority: number, }; export type GrpcMetadataEntry = { enabled?: boolean, name: string, value: string, }; -export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; +export type GrpcRequest = { model: "grpc_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authenticationType: string | null, authentication: Record, description: string, message: string, metadata: Array, method: string | null, name: string, service: string | null, sortPriority: number, url: string, }; -export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; +export type HttpRequest = { model: "http_request", id: string, createdAt: string, updatedAt: string, workspaceId: string, folderId: string | null, authentication: Record, authenticationType: string | null, body: Record, bodyType: string | null, description: string, headers: Array, method: string, name: string, sortPriority: number, url: string, urlParameters: Array, }; export type HttpRequestHeader = { enabled?: boolean, name: string, value: string, }; @@ -22,4 +22,4 @@ export type HttpResponseState = "initialized" | "connected" | "closed"; export type HttpUrlParameter = { enabled?: boolean, name: string, value: string, }; -export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, variables: Array, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; +export type Workspace = { model: "workspace", id: string, createdAt: string, updatedAt: string, name: string, description: string, settingValidateCertificates: boolean, settingFollowRedirects: boolean, settingRequestTimeout: number, }; diff --git a/src-web/components/CommandPalette.tsx b/src-web/components/CommandPalette.tsx index 2b10ea7c..0c45ab72 100644 --- a/src-web/components/CommandPalette.tsx +++ b/src-web/components/CommandPalette.tsx @@ -59,7 +59,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment(); const httpRequestActions = useHttpRequestActions(); const workspaces = useWorkspaces(); - const environments = useEnvironments(); + const { subEnvironments } = useEnvironments(); const recentEnvironments = useRecentEnvironments(); const recentWorkspaces = useRecentWorkspaces(); const requests = useRequests(); @@ -211,7 +211,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { }, [recentRequests, requests]); const sortedEnvironments = useMemo(() => { - return [...environments].sort((a, b) => { + return [...subEnvironments].sort((a, b) => { const aRecentIndex = recentEnvironments.indexOf(a.id); const bRecentIndex = recentEnvironments.indexOf(b.id); @@ -225,7 +225,7 @@ export function CommandPalette({ onClose }: { onClose: () => void }) { return a.createdAt.localeCompare(b.createdAt); } }); - }, [environments, recentEnvironments]); + }, [subEnvironments, recentEnvironments]); const sortedWorkspaces = useMemo(() => { return [...workspaces].sort((a, b) => { diff --git a/src-web/components/EnvironmentActionsDropdown.tsx b/src-web/components/EnvironmentActionsDropdown.tsx index 81fae1b8..f33e0700 100644 --- a/src-web/components/EnvironmentActionsDropdown.tsx +++ b/src-web/components/EnvironmentActionsDropdown.tsx @@ -1,7 +1,6 @@ import classNames from 'classnames'; import { memo, useCallback, useMemo } from 'react'; import { useActiveEnvironment } from '../hooks/useActiveEnvironment'; -import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useEnvironments } from '../hooks/useEnvironments'; import type { ButtonProps } from './core/Button'; import { Button } from './core/Button'; @@ -19,8 +18,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo className, ...buttonProps }: Props) { - const environments = useEnvironments(); - const activeWorkspace = useActiveWorkspace(); + const { subEnvironments, baseEnvironment } = useEnvironments(); const [activeEnvironment, setActiveEnvironmentId] = useActiveEnvironment(); const dialog = useDialog(); @@ -36,7 +34,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo const items: DropdownItem[] = useMemo( () => [ - ...environments.map( + ...subEnvironments.map( (e) => ({ key: e.id, label: e.name, @@ -51,7 +49,7 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo }), [activeEnvironment?.id], ), - ...((environments.length > 0 + ...((subEnvironments.length > 0 ? [{ type: 'separator', label: 'Environments' }] : []) as DropdownItem[]), { @@ -62,11 +60,11 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo onSelect: showEnvironmentDialog, }, ], - [activeEnvironment?.id, environments, setActiveEnvironmentId, showEnvironmentDialog], + [activeEnvironment?.id, subEnvironments, setActiveEnvironmentId, showEnvironmentDialog], ); - const hasWorkspaceVars = - (activeWorkspace?.variables ?? []).filter((v) => v.enabled && (v.name || v.value)).length > 0; + const hasBaseVars = + (baseEnvironment?.variables ?? []).filter((v) => v.enabled && (v.name || v.value)).length > 0; return ( @@ -75,14 +73,14 @@ export const EnvironmentActionsDropdown = memo(function EnvironmentActionsDropdo className={classNames( className, 'text !px-2 truncate', - !activeEnvironment && !hasWorkspaceVars && 'text-text-subtlest italic', + !activeEnvironment && !hasBaseVars && 'text-text-subtlest italic', )} // If no environments, the button simply opens the dialog. // NOTE: We don't create a new button because we want to reuse the hotkey from the menu items - onClick={environments.length === 0 ? showEnvironmentDialog : undefined} + onClick={subEnvironments.length === 0 ? showEnvironmentDialog : undefined} {...buttonProps} > - {activeEnvironment?.name ?? (hasWorkspaceVars ? 'Environment' : 'No Environment')} + {activeEnvironment?.name ?? (hasBaseVars ? 'Environment' : 'No Environment')} ); diff --git a/src-web/components/EnvironmentEditDialog.tsx b/src-web/components/EnvironmentEditDialog.tsx index 0e7b6d50..bb6fb428 100644 --- a/src-web/components/EnvironmentEditDialog.tsx +++ b/src-web/components/EnvironmentEditDialog.tsx @@ -1,15 +1,14 @@ +import type { Environment } from '@yaakapp-internal/models'; import classNames from 'classnames'; import type { ReactNode } from 'react'; import React, { useCallback, useMemo, useState } from 'react'; -import { useActiveWorkspace } from '../hooks/useActiveWorkspace'; import { useCreateEnvironment } from '../hooks/useCreateEnvironment'; import { useDeleteEnvironment } from '../hooks/useDeleteEnvironment'; import { useEnvironments } from '../hooks/useEnvironments'; import { useKeyValue } from '../hooks/useKeyValue'; import { usePrompt } from '../hooks/usePrompt'; import { useUpdateEnvironment } from '../hooks/useUpdateEnvironment'; -import { useUpdateWorkspace } from '../hooks/useUpdateWorkspace'; -import type { Environment, Workspace } from '@yaakapp-internal/models'; +import { Banner } from './core/Banner'; import { Button } from './core/Button'; import { ContextMenu } from './core/Dropdown'; import type { @@ -31,18 +30,15 @@ interface Props { } export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) { - const [selectedEnvironmentId, setSelectedEnvironmentId] = useState( - initialEnvironment?.id ?? null, - ); - const environments = useEnvironments(); const createEnvironment = useCreateEnvironment(); - const activeWorkspace = useActiveWorkspace(); + const { baseEnvironment, subEnvironments, allEnvironments } = useEnvironments(); - const selectedEnvironment = useMemo( - () => environments.find((e) => e.id === selectedEnvironmentId) ?? null, - [environments, selectedEnvironmentId], + const [selectedEnvironmentId, setSelectedEnvironmentId] = useState( + initialEnvironment?.id ?? baseEnvironment?.id ?? null, ); + const selectedEnvironment = allEnvironments.find((e) => e.id === selectedEnvironmentId); + const handleCreateEnvironment = async () => { const e = await createEnvironment.mutateAsync(); if (e == null) return; @@ -59,8 +55,8 @@ export const EnvironmentEditDialog = function ({ initialEnvironment }: Props) {