diff --git a/eslint.config.cjs b/eslint.config.cjs index 50f0bba4..e3cec310 100644 --- a/eslint.config.cjs +++ b/eslint.config.cjs @@ -80,6 +80,7 @@ module.exports = defineConfig([ globalIgnores([ '**/node_modules/', '**/dist/', + '**/build/', '**/.eslintrc.cjs', '**/.prettierrc.cjs', 'src-web/postcss.config.cjs', diff --git a/package-lock.json b/package-lock.json index ced673e8..548a7324 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,8 @@ "plugins/auth-bearer", "plugins/auth-jwt", "plugins/auth-oauth2", - "plugins/exporter-curl", + "plugins/action-copy-curl", + "plugins/action-copy-grpcurl", "plugins/filter-jsonpath", "plugins/filter-xpath", "plugins/importer-curl", @@ -69,6 +70,7 @@ "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "typescript": "^5.8.3", + "vitest": "^3.2.4", "workspaces-run": "^1.0.2" } }, @@ -710,7 +712,6 @@ "os": [ "aix" ], - "peer": true, "engines": { "node": ">=18" } @@ -728,7 +729,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -746,7 +746,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -764,7 +763,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -782,7 +780,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -800,7 +797,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -818,7 +814,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -836,7 +831,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -854,7 +848,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -872,7 +865,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -890,7 +882,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -908,7 +899,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -926,7 +916,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -944,7 +933,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -962,7 +950,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -980,7 +967,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -998,7 +984,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -1016,7 +1001,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1034,7 +1018,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1052,6 +1035,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -1069,7 +1053,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -1087,7 +1070,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -1105,7 +1087,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1123,7 +1104,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1141,7 +1121,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -3267,6 +3246,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/chai": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", + "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3276,6 +3265,13 @@ "@types/ms": "*" } }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", @@ -3770,6 +3766,94 @@ "vite": "^4.2.0 || ^5.0.0" } }, + "node_modules/@vitest/expect": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", + "integrity": "sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz", + "integrity": "sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.4.tgz", + "integrity": "sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.4", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.4.tgz", + "integrity": "sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.4.tgz", + "integrity": "sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.4.tgz", + "integrity": "sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.4", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -3785,6 +3869,14 @@ "integrity": "sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==", "license": "MIT" }, + "node_modules/@yaak/action-copy-curl": { + "resolved": "plugins/action-copy-curl", + "link": true + }, + "node_modules/@yaak/action-copy-grpcurl": { + "resolved": "plugins/action-copy-grpcurl", + "link": true + }, "node_modules/@yaak/auth-basic": { "resolved": "plugins/auth-basic", "link": true @@ -3801,10 +3893,6 @@ "resolved": "plugins/auth-oauth2", "link": true }, - "node_modules/@yaak/exporter-curl": { - "resolved": "plugins/exporter-curl", - "link": true - }, "node_modules/@yaak/filter-jsonpath": { "resolved": "plugins/filter-jsonpath", "link": true @@ -4452,6 +4540,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -4859,6 +4957,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -5026,6 +5134,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -5092,6 +5217,16 @@ "node": ">=4.0.0" } }, + "node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -5744,9 +5879,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -5942,6 +6077,16 @@ "node": ">=0.10.0" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -6427,6 +6572,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -6499,7 +6651,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -6547,7 +6698,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -7270,6 +7420,16 @@ "dev": true, "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -10042,6 +10202,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.4.tgz", + "integrity": "sha512-wJzkKwJrheKtknCOKNEtDK4iqg/MxmZheEMtSTYvnzRdEYaZzmgH976nenp8WdJRdx5Vc1X/9MO0Oszl6ezeXg==", + "dev": true, + "license": "MIT" + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -10071,6 +10238,16 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, "node_modules/make-cancellable-promise": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/make-cancellable-promise/-/make-cancellable-promise-1.3.2.tgz", @@ -12755,6 +12932,23 @@ "node": ">=6" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pdfjs-dist": { "version": "4.4.168", "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.4.168.tgz", @@ -14389,6 +14583,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -14827,6 +15028,13 @@ "stackframe": "^1.3.4" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -14904,6 +15112,13 @@ "node": ">= 0.4" } }, + "node_modules/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -15205,6 +15420,26 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-literal": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", + "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/style-mod": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", @@ -15713,6 +15948,95 @@ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.4.4", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.4.6", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", + "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", + "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-buffer": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", @@ -15903,6 +16227,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -15920,6 +16245,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -15937,6 +16263,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -15954,6 +16281,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -15971,6 +16299,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -15988,6 +16317,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -16005,6 +16335,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16022,6 +16353,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16039,6 +16371,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16056,6 +16389,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16073,6 +16407,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16090,6 +16425,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16107,6 +16443,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16124,6 +16461,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16141,6 +16479,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16158,6 +16497,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16175,6 +16515,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -16192,6 +16533,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16209,6 +16551,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -16226,6 +16569,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -16243,6 +16587,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -16260,6 +16605,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -16277,6 +16623,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -16859,7 +17206,6 @@ "integrity": "sha512-gzLogvGSgX2xyAt0J5qhJ7SmdO5aLdShABkU8Ev7dIl8AcrlFSLcj9GHReSq9pGJF/q5C4CZKdtDlkC6DyvQ3w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.24.2", "postcss": "^8.4.49", @@ -16926,6 +17272,29 @@ } } }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/vite-plugin-static-copy": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-2.2.0.tgz", @@ -17023,6 +17392,129 @@ "vite": "^2 || ^3 || ^4 || ^5 || ^6" } }, + "node_modules/vitest": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.4.tgz", + "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.4", + "@vitest/mocker": "3.2.4", + "@vitest/pretty-format": "^3.2.4", + "@vitest/runner": "3.2.4", + "@vitest/snapshot": "3.2.4", + "@vitest/spy": "3.2.4", + "@vitest/utils": "3.2.4", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.4", + "@vitest/ui": "3.2.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.4.tgz", + "integrity": "sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", @@ -17197,6 +17689,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -17609,7 +18118,7 @@ }, "packages/plugin-runtime-types": { "name": "@yaakapp/api", - "version": "0.6.5", + "version": "0.6.6", "dependencies": { "@types/node": "^22.5.4" }, @@ -17666,6 +18175,14 @@ "typescript": "^5.6.2" } }, + "plugins/action-copy-curl": { + "name": "@yaak/action-copy-curl", + "version": "0.0.1" + }, + "plugins/action-copy-grpcurl": { + "name": "@yaak/action-copy-grpcurl", + "version": "0.0.1" + }, "plugins/auth-basic": { "name": "@yaak/auth-basic", "version": "0.0.1" @@ -17690,7 +18207,8 @@ }, "plugins/exporter-curl": { "name": "@yaak/exporter-curl", - "version": "0.0.1" + "version": "0.0.1", + "extraneous": true }, "plugins/filter-jsonpath": { "name": "@yaak/filter-jsonpath", diff --git a/package.json b/package.json index 130e78e2..33232d4c 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "plugins/auth-bearer", "plugins/auth-jwt", "plugins/auth-oauth2", - "plugins/exporter-curl", + "plugins/action-copy-curl", + "plugins/action-copy-grpcurl", "plugins/filter-jsonpath", "plugins/filter-xpath", "plugins/importer-curl", @@ -85,6 +86,7 @@ "npm-run-all": "^4.1.5", "prettier": "^3.4.2", "typescript": "^5.8.3", + "vitest": "^3.2.4", "workspaces-run": "^1.0.2" } } diff --git a/packages/plugin-runtime-types/src/bindings/gen_events.ts b/packages/plugin-runtime-types/src/bindings/gen_events.ts index 4ae63a3c..db224e39 100644 --- a/packages/plugin-runtime-types/src/bindings/gen_events.ts +++ b/packages/plugin-runtime-types/src/bindings/gen_events.ts @@ -6,6 +6,10 @@ export type BootRequest = { dir: string, watch: boolean, }; export type BootResponse = { name: string, version: string, }; +export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array, }; + +export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; + export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, }; export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: string, args: CallHttpAuthenticationActionArgs, }; @@ -336,14 +340,14 @@ export type GetCookieValueRequest = { name: string, }; export type GetCookieValueResponse = { value: string | null, }; +export type GetGrpcRequestActionsResponse = { actions: Array, pluginRefId: string, }; + export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, }; export type GetHttpAuthenticationConfigResponse = { args: Array, pluginRefId: string, actions?: Array, }; export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, }; -export type GetHttpRequestActionsRequest = Record; - export type GetHttpRequestActionsResponse = { actions: Array, pluginRefId: string, }; export type GetHttpRequestByIdRequest = { id: string, }; @@ -360,6 +364,8 @@ export type GetThemesRequest = Record; export type GetThemesResponse = { themes: Array, }; +export type GrpcRequestAction = { label: string, icon?: Icon, }; + export type HttpAuthenticationAction = { label: string, icon?: Icon, }; export type HttpHeader = { name: string, value: string, }; @@ -376,7 +382,7 @@ export type ImportResponse = { resources: ImportResources, }; export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse; +export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse; export type JsonPrimitive = string | number | boolean | null; @@ -408,6 +414,10 @@ required?: boolean, }; export type PromptTextResponse = { value: string | null, }; +export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; + +export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; + export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; export type RenderHttpRequestResponse = { httpRequest: HttpRequest, }; diff --git a/packages/plugin-runtime-types/src/plugins/Context.ts b/packages/plugin-runtime-types/src/plugins/Context.ts index fadcf0ba..5a481b04 100644 --- a/packages/plugin-runtime-types/src/plugins/Context.ts +++ b/packages/plugin-runtime-types/src/plugins/Context.ts @@ -9,6 +9,8 @@ import type { OpenWindowRequest, PromptTextRequest, PromptTextResponse, + RenderGrpcRequestRequest, + RenderGrpcRequestResponse, RenderHttpRequestRequest, RenderHttpRequestResponse, SendHttpRequestRequest, @@ -45,6 +47,9 @@ export interface Context { listNames(): Promise; getValue(args: GetCookieValueRequest): Promise; }; + grpcRequest: { + render(args: RenderGrpcRequestRequest): Promise; + }; httpRequest: { send(args: SendHttpRequestRequest): Promise; getById(args: GetHttpRequestByIdRequest): Promise; diff --git a/packages/plugin-runtime-types/src/plugins/GrpcRequestActionPlugin.ts b/packages/plugin-runtime-types/src/plugins/GrpcRequestActionPlugin.ts new file mode 100644 index 00000000..f2e7ab82 --- /dev/null +++ b/packages/plugin-runtime-types/src/plugins/GrpcRequestActionPlugin.ts @@ -0,0 +1,6 @@ +import { CallGrpcRequestActionArgs, GrpcRequestAction } from '../bindings/gen_events'; +import type { Context } from './Context'; + +export type GrpcRequestActionPlugin = GrpcRequestAction & { + onSelect(ctx: Context, args: CallGrpcRequestActionArgs): Promise | void; +}; diff --git a/packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts b/packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts index f2ccff9b..0bf34a19 100644 --- a/packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts +++ b/packages/plugin-runtime-types/src/plugins/ImporterPlugin.ts @@ -1,5 +1,5 @@ import { ImportResources } from '../bindings/gen_events'; -import { AtLeast } from '../helpers'; +import { AtLeast, MaybePromise } from '../helpers'; import type { Context } from './Context'; type RootFields = 'name' | 'id' | 'model'; @@ -21,5 +21,8 @@ export type ImportPluginResponse = null | { export type ImporterPlugin = { name: string; description?: string; - onImport(ctx: Context, args: { text: string }): Promise; + onImport( + ctx: Context, + args: { text: string }, + ): MaybePromise; }; diff --git a/packages/plugin-runtime-types/src/plugins/index.ts b/packages/plugin-runtime-types/src/plugins/index.ts index 7118df9b..dbe3d098 100644 --- a/packages/plugin-runtime-types/src/plugins/index.ts +++ b/packages/plugin-runtime-types/src/plugins/index.ts @@ -1,5 +1,6 @@ import { AuthenticationPlugin } from './AuthenticationPlugin'; import type { FilterPlugin } from './FilterPlugin'; +import { GrpcRequestActionPlugin } from './GrpcRequestActionPlugin'; import type { HttpRequestActionPlugin } from './HttpRequestActionPlugin'; import type { ImporterPlugin } from './ImporterPlugin'; import type { TemplateFunctionPlugin } from './TemplateFunctionPlugin'; @@ -16,5 +17,6 @@ export type PluginDefinition = { filter?: FilterPlugin; authentication?: AuthenticationPlugin; httpRequestActions?: HttpRequestActionPlugin[]; + grpcRequestActions?: GrpcRequestActionPlugin[]; templateFunctions?: TemplateFunctionPlugin[]; }; diff --git a/packages/plugin-runtime/src/PluginInstance.ts b/packages/plugin-runtime/src/PluginInstance.ts index a5ee05cb..e227ddac 100644 --- a/packages/plugin-runtime/src/PluginInstance.ts +++ b/packages/plugin-runtime/src/PluginInstance.ts @@ -7,7 +7,7 @@ import { GetCookieValueRequest, GetCookieValueResponse, GetHttpRequestByIdResponse, - GetKeyValueResponse, + GetKeyValueResponse, GrpcRequestAction, HttpAuthenticationAction, HttpRequestAction, InternalEvent, @@ -16,6 +16,7 @@ import { PluginWindowContext, PromptTextResponse, RenderHttpRequestResponse, + RenderGrpcRequestResponse, SendHttpRequestResponse, TemplateFunction, TemplateFunctionArg, @@ -145,6 +146,24 @@ export class PluginInstance { return; } + if ( + payload.type === 'get_grpc_request_actions_request' && + Array.isArray(this.#mod?.grpcRequestActions) + ) { + const reply: GrpcRequestAction[] = this.#mod.grpcRequestActions.map((a) => ({ + ...a, + // Add everything except onSelect + onSelect: undefined, + })); + const replyPayload: InternalEventPayload = { + type: 'get_grpc_request_actions_response', + pluginRefId: this.#workerData.pluginRefId, + actions: reply, + }; + this.#sendPayload(windowContext, replyPayload, replyId); + return; + } + if ( payload.type === 'get_http_request_actions_request' && Array.isArray(this.#mod?.httpRequestActions) @@ -208,13 +227,12 @@ export class PluginInstance { if (payload.type === 'get_http_authentication_config_request' && this.#mod?.authentication) { const { args, actions } = this.#mod.authentication; const resolvedArgs: FormInput[] = []; - for (let i = 0; i < args.length; i++) { - let v = args[i]; - if ('dynamic' in v) { + for (const v of args) { + if (v && 'dynamic' in v) { const dynamicAttrs = await v.dynamic(ctx, payload); const { dynamic, ...other } = v; resolvedArgs.push({ ...other, ...dynamicAttrs } as FormInput); - } else { + } else if (v) { resolvedArgs.push(v); } } @@ -275,6 +293,18 @@ export class PluginInstance { } } + if ( + payload.type === 'call_grpc_request_action_request' && + Array.isArray(this.#mod.grpcRequestActions) + ) { + const action = this.#mod.grpcRequestActions[payload.index]; + if (typeof action?.onSelect === 'function') { + await action.onSelect(ctx, payload.args); + this.#sendEmpty(windowContext, replyId); + return; + } + } + if ( payload.type === 'call_template_function_request' && Array.isArray(this.#mod?.templateFunctions) @@ -472,6 +502,19 @@ export class PluginInstance { return httpResponses; }, }, + grpcRequest: { + render: async (args) => { + const payload = { + type: 'render_grpc_request_request', + ...args, + } as const; + const { grpcRequest } = await this.#sendAndWaitForReply( + event.windowContext, + payload, + ); + return grpcRequest; + }, + }, httpRequest: { getById: async (args) => { const payload = { @@ -596,20 +639,20 @@ function applyFormInputDefaults( } } -const watchedFiles: Record = {}; +const watchedFiles: Record = {}; /** - * Watch a file and trigger callback on change. + * Watch a file and trigger a callback on change. * * We also track the stat for each file because fs.watch() will - * trigger a "change" event when the access date changes + * trigger a "change" event when the access date changes. */ -function watchFile(filepath: string, cb: (filepath: string) => void) { +function watchFile(filepath: string, cb: () => void) { watch(filepath, () => { - const stat = statSync(filepath); - if (stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) { - cb(filepath); + const stat = statSync(filepath, { throwIfNoEntry: false }); + if (stat == null || stat.mtimeMs !== watchedFiles[filepath]?.mtimeMs) { + watchedFiles[filepath] = stat ?? null; + cb(); } - watchedFiles[filepath] = stat; }); } diff --git a/plugins/exporter-curl/package.json b/plugins/action-copy-curl/package.json similarity index 51% rename from plugins/exporter-curl/package.json rename to plugins/action-copy-curl/package.json index 4e76f27e..8acbf6e6 100644 --- a/plugins/exporter-curl/package.json +++ b/plugins/action-copy-curl/package.json @@ -1,9 +1,10 @@ { - "name": "@yaak/exporter-curl", + "name": "@yaak/action-copy-curl", "private": true, "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/exporter-curl/src/index.ts b/plugins/action-copy-curl/src/index.ts similarity index 74% rename from plugins/exporter-curl/src/index.ts rename to plugins/action-copy-curl/src/index.ts index e8fa3415..8f04b878 100644 --- a/plugins/exporter-curl/src/index.ts +++ b/plugins/action-copy-curl/src/index.ts @@ -1,18 +1,27 @@ -import { HttpRequest, PluginDefinition } from '@yaakapp/api'; +import type { HttpRequest, PluginDefinition } from '@yaakapp/api'; const NEWLINE = '\\\n '; export const plugin: PluginDefinition = { - httpRequestActions: [{ - label: 'Copy as Curl', - icon: 'copy', - async onSelect(ctx, args) { - const rendered_request = await ctx.httpRequest.render({ httpRequest: args.httpRequest, purpose: 'preview' }); - const data = await convertToCurl(rendered_request); - await ctx.clipboard.copyText(data); - await ctx.toast.show({ message: 'Curl copied to clipboard', icon: 'copy', color: 'success' }); + httpRequestActions: [ + { + label: 'Copy as Curl', + icon: 'copy', + async onSelect(ctx, args) { + const rendered_request = await ctx.httpRequest.render({ + httpRequest: args.httpRequest, + purpose: 'preview', + }); + const data = await convertToCurl(rendered_request); + await ctx.clipboard.copyText(data); + await ctx.toast.show({ + message: 'Command copied to clipboard', + icon: 'copy', + color: 'success', + }); + }, }, - }], + ], }; export async function convertToCurl(request: Partial) { @@ -22,7 +31,6 @@ export async function convertToCurl(request: Partial) { if (request.method) xs.push('-X', request.method); if (request.url) xs.push(quote(request.url)); - xs.push(NEWLINE); // Add URL params @@ -51,7 +59,10 @@ export async function convertToCurl(request: Partial) { xs.push(NEWLINE); } } else if (typeof request.body?.query === 'string') { - const body = { query: request.body.query || '', variables: maybeParseJSON(request.body.variables, undefined) }; + const body = { + query: request.body.query || '', + variables: maybeParseJSON(request.body.variables, undefined), + }; xs.push('--data-raw', `${quote(JSON.stringify(body))}`); xs.push(NEWLINE); } else if (typeof request.body?.text === 'string') { @@ -84,7 +95,7 @@ export async function convertToCurl(request: Partial) { } function quote(arg: string): string { - const escaped = arg.replace(/'/g, '\\\''); + const escaped = arg.replace(/'/g, "\\'"); return `'${escaped}'`; } @@ -92,10 +103,10 @@ function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean { return v.enabled !== false && !!v.name; } -function maybeParseJSON(v: any, fallback: any): string { +function maybeParseJSON(v: string, fallback: T) { try { return JSON.parse(v); - } catch (err) { + } catch { return fallback; } } diff --git a/plugins/exporter-curl/tests/index.test.ts b/plugins/action-copy-curl/tests/index.test.ts similarity index 100% rename from plugins/exporter-curl/tests/index.test.ts rename to plugins/action-copy-curl/tests/index.test.ts diff --git a/plugins/action-copy-grpcurl/package.json b/plugins/action-copy-grpcurl/package.json new file mode 100644 index 00000000..b7243d85 --- /dev/null +++ b/plugins/action-copy-grpcurl/package.json @@ -0,0 +1,10 @@ +{ + "name": "@yaak/action-copy-grpcurl", + "private": true, + "version": "0.0.1", + "scripts": { + "build": "yaakcli build ./src/index.js", + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" + } +} diff --git a/plugins/action-copy-grpcurl/src/index.ts b/plugins/action-copy-grpcurl/src/index.ts new file mode 100644 index 00000000..07da1c42 --- /dev/null +++ b/plugins/action-copy-grpcurl/src/index.ts @@ -0,0 +1,134 @@ +import type { GrpcRequest, PluginDefinition } from '@yaakapp/api'; +import path from 'node:path'; + +const NEWLINE = '\\\n '; + +export const plugin: PluginDefinition = { + grpcRequestActions: [ + { + label: 'Copy as gRPCurl', + icon: 'copy', + async onSelect(ctx, args) { + const rendered_request = await ctx.grpcRequest.render({ + grpcRequest: args.grpcRequest, + purpose: 'preview', + }); + const data = await convert(rendered_request, args.protoFiles); + await ctx.clipboard.copyText(data); + await ctx.toast.show({ + message: 'Command copied to clipboard', + icon: 'copy', + color: 'success', + }); + }, + }, + ], +}; + +export async function convert(request: Partial, allProtoFiles: string[]) { + const xs = ['grpcurl']; + + if (request.url?.startsWith('http://')) { + xs.push('-plaintext'); + } + + const protoIncludes = allProtoFiles.filter((f) => !f.endsWith('.proto')); + const protoFiles = allProtoFiles.filter((f) => f.endsWith('.proto')); + + const inferredIncludes = new Set(); + for (const f of protoFiles) { + const protoDir = findParentProtoDir(f); + if (protoDir) { + inferredIncludes.add(protoDir); + } else { + inferredIncludes.add(path.join(f, '..')); + inferredIncludes.add(path.join(f, '..', '..')); + } + } + + for (const f of protoIncludes) { + xs.push('-import-path', quote(f)); + xs.push(NEWLINE); + } + + for (const f of inferredIncludes.values()) { + xs.push('-import-path', quote(f)); + xs.push(NEWLINE); + } + + for (const f of protoFiles) { + xs.push('-proto', quote(f)); + xs.push(NEWLINE); + } + + // Add headers + for (const h of (request.metadata ?? []).filter(onlyEnabled)) { + xs.push('-H', quote(`${h.name}: ${h.value}`)); + xs.push(NEWLINE); + } + + // Add basic authentication + if (request.authenticationType === 'basic') { + const user = request.authentication?.username ?? ''; + const pass = request.authentication?.password ?? ''; + const encoded = btoa(`${user}:${pass}`); + xs.push('-H', quote(`Authorization: Basic ${encoded}`)); + xs.push(NEWLINE); + } else if (request.authenticationType === 'bearer') { + // Add bearer authentication + xs.push('-H', quote(`Authorization: Bearer ${request.authentication?.token ?? ''}`)); + xs.push(NEWLINE); + } + + // Add form params + if (request.message) { + xs.push('-d', `${quote(JSON.stringify(JSON.parse(request.message)))}`); + xs.push(NEWLINE); + } + + // Add the server address + if (request.url) { + const server = request.url.replace(/^https?:\/\//, ''); // remove protocol + xs.push(server); + } + + // Add service + method + if (request.service && request.method) { + xs.push(`${request.service}/${request.method}`); + } + + xs.push(NEWLINE); + + // Remove trailing newline + if (xs[xs.length - 1] === NEWLINE) { + xs.splice(xs.length - 1, 1); + } + + return xs.join(' '); +} + +function quote(arg: string): string { + const escaped = arg.replace(/'/g, "\\'"); + return `'${escaped}'`; +} + +function onlyEnabled(v: { name?: string; enabled?: boolean }): boolean { + return v.enabled !== false && !!v.name; +} + +function findParentProtoDir(startPath: string): string | null { + let dir = path.resolve(startPath); + + while (true) { + if (path.basename(dir) === 'proto') { + return dir; + } + + const parent = path.dirname(dir); + if (parent === dir) { + return null; // Reached root + } + + dir = parent; + } +} diff --git a/plugins/action-copy-grpcurl/tests/index.test.ts b/plugins/action-copy-grpcurl/tests/index.test.ts new file mode 100644 index 00000000..7ee2ec98 --- /dev/null +++ b/plugins/action-copy-grpcurl/tests/index.test.ts @@ -0,0 +1,110 @@ +import { describe, expect, test } from 'vitest'; +import { convert } from '../src'; + +describe('exporter-curl', () => { + test('Simple example', async () => { + expect( + await convert( + { + url: 'https://yaak.app', + }, + [], + ), + ).toEqual([`grpcurl yaak.app`].join(` \\\n `)); + }); + test('Basic metadata', async () => { + expect( + await convert( + { + url: 'https://yaak.app', + metadata: [ + { name: 'aaa', value: 'AAA' }, + { enabled: true, name: 'bbb', value: 'BBB' }, + { enabled: false, name: 'disabled', value: 'ddd' }, + ], + }, + [], + ), + ).toEqual([`grpcurl -H 'aaa: AAA'`, `-H 'bbb: BBB'`, `yaak.app`].join(` \\\n `)); + }); + test('Single proto file', async () => { + expect(await convert({ url: 'https://yaak.app' }, ['/foo/bar/baz.proto'])).toEqual( + [ + `grpcurl -import-path '/foo/bar'`, + `-import-path '/foo'`, + `-proto '/foo/bar/baz.proto'`, + `yaak.app`, + ].join(` \\\n `), + ); + }); + test('Multiple proto files, same dir', async () => { + expect( + await convert({ url: 'https://yaak.app' }, ['/foo/bar/aaa.proto', '/foo/bar/bbb.proto']), + ).toEqual( + [ + `grpcurl -import-path '/foo/bar'`, + `-import-path '/foo'`, + `-proto '/foo/bar/aaa.proto'`, + `-proto '/foo/bar/bbb.proto'`, + `yaak.app`, + ].join(` \\\n `), + ); + }); + test('Multiple proto files, different dir', async () => { + expect( + await convert({ url: 'https://yaak.app' }, ['/aaa/bbb/ccc.proto', '/xxx/yyy/zzz.proto']), + ).toEqual( + [ + `grpcurl -import-path '/aaa/bbb'`, + `-import-path '/aaa'`, + `-import-path '/xxx/yyy'`, + `-import-path '/xxx'`, + `-proto '/aaa/bbb/ccc.proto'`, + `-proto '/xxx/yyy/zzz.proto'`, + `yaak.app`, + ].join(` \\\n `), + ); + }); + test('Single include dir', async () => { + expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb'])).toEqual( + [`grpcurl -import-path '/aaa/bbb'`, `yaak.app`].join(` \\\n `), + ); + }); + test('Multiple include dir', async () => { + expect(await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy'])).toEqual( + [`grpcurl -import-path '/aaa/bbb'`, `-import-path '/xxx/yyy'`, `yaak.app`].join(` \\\n `), + ); + }); + test('Mixed proto and dirs', async () => { + expect( + await convert({ url: 'https://yaak.app' }, ['/aaa/bbb', '/xxx/yyy', '/foo/bar.proto']), + ).toEqual( + [ + `grpcurl -import-path '/aaa/bbb'`, + `-import-path '/xxx/yyy'`, + `-import-path '/foo'`, + `-import-path '/'`, + `-proto '/foo/bar.proto'`, + `yaak.app`, + ].join(` \\\n `), + ); + }); + test('Sends data', async () => { + expect( + await convert( + { + url: 'https://yaak.app', + message: JSON.stringify({ foo: 'bar', baz: 1.0 }, null, 2), + }, + ['/foo.proto'], + ), + ).toEqual( + [ + `grpcurl -import-path '/'`, + `-proto '/foo.proto'`, + `-d '{"foo":"bar","baz":1}'`, + `yaak.app`, + ].join(` \\\n `), + ); + }); +}); diff --git a/plugins/auth-basic/package.json b/plugins/auth-basic/package.json index 5fa84a90..4ef73b36 100644 --- a/plugins/auth-basic/package.json +++ b/plugins/auth-basic/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/auth-bearer/package.json b/plugins/auth-bearer/package.json index 7c6efd62..cf45aa96 100644 --- a/plugins/auth-bearer/package.json +++ b/plugins/auth-bearer/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/auth-jwt/package.json b/plugins/auth-jwt/package.json index 185a514f..f498756d 100644 --- a/plugins/auth-jwt/package.json +++ b/plugins/auth-jwt/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "jsonwebtoken": "^9.0.2" diff --git a/plugins/auth-oauth2/package.json b/plugins/auth-oauth2/package.json index 178305a8..39ed7b12 100644 --- a/plugins/auth-oauth2/package.json +++ b/plugins/auth-oauth2/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/filter-jsonpath/package.json b/plugins/filter-jsonpath/package.json index d2bebb19..8020d3bd 100644 --- a/plugins/filter-jsonpath/package.json +++ b/plugins/filter-jsonpath/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "jsonpath-plus": "^10.3.0" diff --git a/plugins/filter-xpath/package.json b/plugins/filter-xpath/package.json index e663b4d5..125a5f0d 100644 --- a/plugins/filter-xpath/package.json +++ b/plugins/filter-xpath/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "@xmldom/xmldom": "^0.8.10", diff --git a/plugins/importer-curl/package.json b/plugins/importer-curl/package.json index c4da3902..61af4af8 100644 --- a/plugins/importer-curl/package.json +++ b/plugins/importer-curl/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "shell-quote": "^1.8.1" diff --git a/plugins/importer-insomnia/package.json b/plugins/importer-insomnia/package.json index 424c63a4..13ad05fd 100644 --- a/plugins/importer-insomnia/package.json +++ b/plugins/importer-insomnia/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "yaml": "^2.4.2" diff --git a/plugins/importer-openapi/package.json b/plugins/importer-openapi/package.json index 753322aa..6d07da84 100644 --- a/plugins/importer-openapi/package.json +++ b/plugins/importer-openapi/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "openapi-to-postmanv2": "^5.0.0", diff --git a/plugins/importer-openapi/src/index.ts b/plugins/importer-openapi/src/index.ts index 2080dda4..9b765779 100644 --- a/plugins/importer-openapi/src/index.ts +++ b/plugins/importer-openapi/src/index.ts @@ -1,32 +1,23 @@ -import { Context, Environment, Folder, HttpRequest, PluginDefinition, Workspace } from '@yaakapp/api'; +import { convertPostman } from '@yaak/importer-postman/src'; +import type { Context, PluginDefinition } from '@yaakapp/api'; +import type { ImportPluginResponse } from '@yaakapp/api/lib/plugins/ImporterPlugin'; import { convert } from 'openapi-to-postmanv2'; -import { convertPostman } from '@yaakapp/importer-postman/src'; - -type AtLeast = Partial & Pick; - -interface ExportResources { - workspaces: AtLeast[]; - environments: AtLeast[]; - httpRequests: AtLeast[]; - folders: AtLeast[]; -} export const plugin: PluginDefinition = { importer: { name: 'OpenAPI', description: 'Import OpenAPI collections', onImport(_ctx: Context, args: { text: string }) { - return convertOpenApi(args.text) as any; + return convertOpenApi(args.text); }, }, }; -export async function convertOpenApi( - contents: string, -): Promise<{ resources: ExportResources } | undefined> { +export async function convertOpenApi(contents: string): Promise { let postmanCollection; try { postmanCollection = await new Promise((resolve, reject) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any convert({ type: 'string', data: contents }, {}, (err, result: any) => { if (err != null) reject(err); @@ -35,7 +26,7 @@ export async function convertOpenApi( } }); }); - } catch (err) { + } catch { // Probably not an OpenAPI file, so skip it return undefined; } diff --git a/plugins/importer-postman/package.json b/plugins/importer-postman/package.json index 7130f779..7dcf5f8e 100644 --- a/plugins/importer-postman/package.json +++ b/plugins/importer-postman/package.json @@ -5,6 +5,7 @@ "main": "./build/index.js", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/importer-postman/src/index.ts b/plugins/importer-postman/src/index.ts index b73aee15..cca8e77c 100644 --- a/plugins/importer-postman/src/index.ts +++ b/plugins/importer-postman/src/index.ts @@ -1,13 +1,15 @@ -import { +import type { Context, Environment, Folder, HttpRequest, HttpRequestHeader, HttpUrlParameter, + PartialImportResources, PluginDefinition, Workspace, } from '@yaakapp/api'; +import type { ImportPluginResponse } from '@yaakapp/api/lib/plugins/ImporterPlugin'; const POSTMAN_2_1_0_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'; const POSTMAN_2_0_0_SCHEMA = 'https://schema.getpostman.com/json/collection/v2.0.0/collection.json'; @@ -27,19 +29,19 @@ export const plugin: PluginDefinition = { name: 'Postman', description: 'Import postman collections', onImport(_ctx: Context, args: { text: string }) { - return convertPostman(args.text) as any; + return convertPostman(args.text); }, }, }; -export function convertPostman( - contents: string, -): { resources: ExportResources } | undefined { +export function convertPostman(contents: string): ImportPluginResponse | undefined { const root = parseJSONToRecord(contents); if (root == null) return; const info = toRecord(root.info); - const isValidSchema = VALID_SCHEMAS.includes(info.schema); + const isValidSchema = VALID_SCHEMAS.includes( + typeof info.schema === 'string' ? info.schema : 'n/a', + ); if (!isValidSchema || !Array.isArray(root.item)) { return; } @@ -53,11 +55,17 @@ export function convertPostman( folders: [], }; + const rawDescription = info.description; + const description = + typeof rawDescription === 'object' && rawDescription !== null && 'content' in rawDescription + ? String(rawDescription.content) + : String(rawDescription); + const workspace: ExportResources['workspaces'][0] = { model: 'workspace', id: generateId('workspace'), - name: info.name || 'Postman Import', - description: info.description?.content ?? info.description, + name: info.name ? String(info.name) : 'Postman Import', + description, }; exportResources.workspaces.push(workspace); @@ -68,14 +76,14 @@ export function convertPostman( name: 'Global Variables', workspaceId: workspace.id, variables: - root.variable?.map((v: any) => ({ + toArray<{ key: string; value: string }>(root.variable).map((v) => ({ name: v.key, value: v.value, })) ?? [], }; exportResources.environments.push(environment); - const importItem = (v: Record, folderId: string | null = null) => { + const importItem = (v: Record, folderId: string | null = null) => { if (typeof v.name === 'string' && Array.isArray(v.item)) { const folder: ExportResources['folders'][0] = { model: 'folder', @@ -94,7 +102,11 @@ export function convertPostman( const requestAuthPath = importAuth(r.auth); const authPatch = requestAuthPath.authenticationType == null ? globalAuth : requestAuthPath; - const headers: HttpRequestHeader[] = toArray(r.header).map((h) => { + const headers: HttpRequestHeader[] = toArray<{ + key: string; + value: string; + disabled?: boolean; + }>(r.header).map((h) => { return { name: h.key, value: h.value, @@ -104,7 +116,9 @@ export function convertPostman( // Add body headers only if they don't already exist for (const bodyPatchHeader of bodyPatch.headers) { - const existingHeader = headers.find(h => h.name.toLowerCase() === bodyPatchHeader.name.toLowerCase()); + const existingHeader = headers.find( + (h) => h.name.toLowerCase() === bodyPatchHeader.name.toLowerCase(), + ); if (existingHeader) { continue; } @@ -119,8 +133,8 @@ export function convertPostman( workspaceId: workspace.id, folderId, name: v.name, - description: v.description || undefined, - method: r.method || 'GET', + description: v.description ? String(v.description) : undefined, + method: typeof r.method === 'string' ? r.method : 'GET', url, urlParameters, body: bodyPatch.body, @@ -139,17 +153,19 @@ export function convertPostman( importItem(item); } - const resources = deleteUndefinedAttrs(convertTemplateSyntax(exportResources)); + const resources = deleteUndefinedAttrs( + convertTemplateSyntax(exportResources), + ) as PartialImportResources; return { resources }; } -function convertUrl(url: string | any): Pick { - if (typeof url === 'string') { - return { url, urlParameters: [] }; +function convertUrl(rawUrl: string | unknown): Pick { + if (typeof rawUrl === 'string') { + return { url: rawUrl, urlParameters: [] }; } - url = toRecord(url); + const url = toRecord(rawUrl); let v = ''; @@ -199,10 +215,8 @@ function convertUrl(url: string | any): Pick { - const auth = toRecord(rawAuth); +function importAuth(rawAuth: unknown): Pick { + const auth = toRecord<{ username?: string; password?: string; token?: string }>(rawAuth); if ('basic' in auth) { return { authenticationType: 'basic', @@ -223,8 +237,22 @@ function importAuth( } } -function importBody(rawBody: any): Pick { - const body = toRecord(rawBody); +function importBody(rawBody: unknown): Pick { + const body = toRecord(rawBody) as { + mode: string; + graphql: { query?: string; variables?: string }; + urlencoded?: { key?: string; value?: string; disabled?: boolean }[]; + formdata?: { + key?: string; + value?: string; + disabled?: boolean; + contentType?: string; + src?: string; + }[]; + raw?: string; + options?: { raw?: { language?: string } }; + file?: { src?: string }; + }; if (body.mode === 'graphql') { return { headers: [ @@ -237,7 +265,10 @@ function importBody(rawBody: any): Pick ({ + form: toArray[0]>(body.urlencoded).map((f) => ({ enabled: !f.disabled, name: f.key ?? '', value: f.value ?? '', @@ -272,19 +303,19 @@ function importBody(rawBody: any): Pick + form: toArray[0]>(body.formdata).map((f) => f.src != null ? { - enabled: !f.disabled, - contentType: f.contentType ?? null, - name: f.key ?? '', - file: f.src ?? '', - } + enabled: !f.disabled, + contentType: f.contentType ?? null, + name: f.key ?? '', + file: f.src ?? '', + } : { - enabled: !f.disabled, - name: f.key ?? '', - value: f.value ?? '', - }, + enabled: !f.disabled, + name: f.key ?? '', + value: f.value ?? '', + }, ), }, }; @@ -315,21 +346,23 @@ function importBody(rawBody: any): Pick | null { +function parseJSONToRecord(jsonStr: string): Record | null { try { return toRecord(JSON.parse(jsonStr)); - } catch (err) { + } catch { + return null; } - return null; } -function toRecord(value: any): Record { - if (Object.prototype.toString.call(value) === '[object Object]') return value; - else return {}; +function toRecord(value: Record | unknown): Record { + if (value && typeof value === 'object' && !Array.isArray(value)) { + return value as Record; + } + return {}; } -function toArray(value: any): any[] { - if (Object.prototype.toString.call(value) === '[object Array]') return value; +function toArray(value: unknown): T[] { + if (Object.prototype.toString.call(value) === '[object Array]') return value as T[]; else return []; } diff --git a/plugins/importer-yaak/package.json b/plugins/importer-yaak/package.json index 4b4dd3ef..60d2cbbe 100644 --- a/plugins/importer-yaak/package.json +++ b/plugins/importer-yaak/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.js", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-cookie/package.json b/plugins/template-function-cookie/package.json index d099a01b..2d9e9d43 100644 --- a/plugins/template-function-cookie/package.json +++ b/plugins/template-function-cookie/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-encode/package.json b/plugins/template-function-encode/package.json index 2bafea95..1ea45d85 100644 --- a/plugins/template-function-encode/package.json +++ b/plugins/template-function-encode/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-encode/src/index.ts b/plugins/template-function-encode/src/index.ts index d829eedf..0a9f8d3d 100644 --- a/plugins/template-function-encode/src/index.ts +++ b/plugins/template-function-encode/src/index.ts @@ -1,4 +1,4 @@ -import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; +import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; export const plugin: PluginDefinition = { templateFunctions: [ @@ -7,7 +7,7 @@ export const plugin: PluginDefinition = { description: 'Encode a value to base64', args: [{ label: 'Plain Text', type: 'text', name: 'value', multiLine: true }], async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { - return Buffer.from(args.values.value ?? '').toString('base64'); + return Buffer.from(String(args.values.value ?? '')).toString('base64'); }, }, { @@ -15,7 +15,7 @@ export const plugin: PluginDefinition = { description: 'Decode a value from base64', args: [{ label: 'Encoded Value', type: 'text', name: 'value', multiLine: true }], async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { - return Buffer.from(args.values.value ?? '', 'base64').toString('utf-8'); + return Buffer.from(String(args.values.value ?? ''), 'base64').toString('utf-8'); }, }, { @@ -23,7 +23,7 @@ export const plugin: PluginDefinition = { description: 'Encode a value for use in a URL (percent-encoding)', args: [{ label: 'Plain Text', type: 'text', name: 'value', multiLine: true }], async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { - return encodeURIComponent(args.values.value ?? ''); + return encodeURIComponent(String(args.values.value ?? '')); }, }, { @@ -32,7 +32,7 @@ export const plugin: PluginDefinition = { args: [{ label: 'Encoded Value', type: 'text', name: 'value', multiLine: true }], async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { try { - return decodeURIComponent(args.values.value ?? ''); + return decodeURIComponent(String(args.values.value ?? '')); } catch { return ''; } diff --git a/plugins/template-function-fs/package.json b/plugins/template-function-fs/package.json index 82aeb094..fe567f6c 100644 --- a/plugins/template-function-fs/package.json +++ b/plugins/template-function-fs/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-fs/src/index.ts b/plugins/template-function-fs/src/index.ts index c783ea4f..5adb8dec 100644 --- a/plugins/template-function-fs/src/index.ts +++ b/plugins/template-function-fs/src/index.ts @@ -1,19 +1,21 @@ -import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; +import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; import fs from 'node:fs'; export const plugin: PluginDefinition = { - templateFunctions: [{ - name: 'fs.readFile', - description: 'Read the contents of a file as utf-8', - args: [{ title: 'Select File', type: 'file', name: 'path', label: 'File' }], - async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { - if (!args.values.path) return null; + templateFunctions: [ + { + name: 'fs.readFile', + description: 'Read the contents of a file as utf-8', + args: [{ title: 'Select File', type: 'file', name: 'path', label: 'File' }], + async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { + if (!args.values.path) return null; - try { - return fs.promises.readFile(args.values.path, 'utf-8'); - } catch (err) { - return null; - } + try { + return fs.promises.readFile(String(args.values.path ?? ''), 'utf-8'); + } catch { + return null; + } + }, }, - }], + ], }; diff --git a/plugins/template-function-hash/package.json b/plugins/template-function-hash/package.json index 905f2a85..ef01bafd 100755 --- a/plugins/template-function-hash/package.json +++ b/plugins/template-function-hash/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-json/package.json b/plugins/template-function-json/package.json index cbf545cd..0f1ef553 100755 --- a/plugins/template-function-json/package.json +++ b/plugins/template-function-json/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "jsonpath-plus": "^10.3.0" diff --git a/plugins/template-function-prompt/package.json b/plugins/template-function-prompt/package.json index b65e6d78..1b3956ad 100644 --- a/plugins/template-function-prompt/package.json +++ b/plugins/template-function-prompt/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-prompt/src/index.ts b/plugins/template-function-prompt/src/index.ts index a40ce13e..b780ecaf 100644 --- a/plugins/template-function-prompt/src/index.ts +++ b/plugins/template-function-prompt/src/index.ts @@ -1,4 +1,4 @@ -import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; +import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; export const plugin: PluginDefinition = { templateFunctions: [{ @@ -15,10 +15,10 @@ export const plugin: PluginDefinition = { return await ctx.prompt.text({ id: `prompt-${args.values.label}`, - label: args.values.title ?? '', - title: args.values.title ?? '', - defaultValue: args.values.defaultValue, - placeholder: args.values.placeholder, + label: String(args.values.title ?? ''), + title: String(args.values.title ?? ''), + defaultValue: String(args.values.defaultValue), + placeholder: String(args.values.placeholder), }); }, }], diff --git a/plugins/template-function-regex/package.json b/plugins/template-function-regex/package.json index 3a7aff4f..403310f2 100644 --- a/plugins/template-function-regex/package.json +++ b/plugins/template-function-regex/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-regex/src/index.ts b/plugins/template-function-regex/src/index.ts index a62367cc..5f30f1b3 100644 --- a/plugins/template-function-regex/src/index.ts +++ b/plugins/template-function-regex/src/index.ts @@ -1,28 +1,32 @@ -import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; +import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; export const plugin: PluginDefinition = { - templateFunctions: [{ - name: 'regex.match', - description: 'Extract', - args: [ - { - type: 'text', - name: 'regex', - label: 'Regular Expression', - placeholder: '^\w+=(?\w*)$', - defaultValue: '^(.*)$', - description: 'A JavaScript regular expression, evaluated using the Node.js RegExp engine. Capture groups or named groups can be used to extract values.', - }, - { type: 'text', name: 'input', label: 'Input Text', multiLine: true }, - ], - async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { - if (!args.values.regex) return ''; + templateFunctions: [ + { + name: 'regex.match', + description: 'Extract', + args: [ + { + type: 'text', + name: 'regex', + label: 'Regular Expression', + placeholder: '^\\w+=(?\\w*)$', + defaultValue: '^(.*)$', + description: + 'A JavaScript regular expression, evaluated using the Node.js RegExp engine. Capture groups or named groups can be used to extract values.', + }, + { type: 'text', name: 'input', label: 'Input Text', multiLine: true }, + ], + async onRender(_ctx: Context, args: CallTemplateFunctionArgs): Promise { + if (!args.values.regex || !args.values.input) return ''; - const regex = new RegExp(String(args.values.regex)); - const match = args.values.input?.match(regex); - return match?.groups - ? Object.values(match.groups)[0] ?? '' - : match?.[1] ?? match?.[0] ?? ''; + const input = String(args.values.input); + const regex = new RegExp(String(args.values.regex)); + const match = input.match(regex); + return match?.groups + ? (Object.values(match.groups)[0] ?? '') + : (match?.[1] ?? match?.[0] ?? ''); + }, }, - }], + ], }; diff --git a/plugins/template-function-request/package.json b/plugins/template-function-request/package.json index 94109a84..5ea92e24 100755 --- a/plugins/template-function-request/package.json +++ b/plugins/template-function-request/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/plugins/template-function-request/src/index.ts b/plugins/template-function-request/src/index.ts index 0567d587..df5d2ac9 100755 --- a/plugins/template-function-request/src/index.ts +++ b/plugins/template-function-request/src/index.ts @@ -1,21 +1,26 @@ -import { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; +import type { CallTemplateFunctionArgs, Context, PluginDefinition } from '@yaakapp/api'; export const plugin: PluginDefinition = { templateFunctions: [ { name: 'request.body', - args: [{ - name: 'requestId', - label: 'Http Request', - type: 'http_request', - }], + args: [ + { + name: 'requestId', + label: 'Http Request', + type: 'http_request', + }, + ], async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise { - const httpRequest = await ctx.httpRequest.getById({ id: args.values.requestId ?? 'n/a' }); + const requestId = String(args.values.requestId ?? 'n/a'); + const httpRequest = await ctx.httpRequest.getById({ id: requestId }); if (httpRequest == null) return null; - return String(await ctx.templates.render({ - data: httpRequest.body?.text ?? '', - purpose: args.purpose, - })); + return String( + await ctx.templates.render({ + data: httpRequest.body?.text ?? '', + purpose: args.purpose, + }), + ); }, }, { @@ -30,15 +35,22 @@ export const plugin: PluginDefinition = { name: 'header', label: 'Header Name', type: 'text', - }], + }, + ], async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise { - const httpRequest = await ctx.httpRequest.getById({ id: args.values.requestId ?? 'n/a' }); + const headerName = String(args.values.header ?? ''); + const requestId = String(args.values.requestId ?? 'n/a'); + const httpRequest = await ctx.httpRequest.getById({ id: requestId }); if (httpRequest == null) return null; - const header = httpRequest.headers.find(h => h.name.toLowerCase() === args.values.header?.toLowerCase()); - return String(await ctx.templates.render({ - data: header?.value ?? '', - purpose: args.purpose, - })); + const header = httpRequest.headers.find( + (h) => h.name.toLowerCase() === headerName.toLowerCase(), + ); + return String( + await ctx.templates.render({ + data: header?.value ?? '', + purpose: args.purpose, + }), + ); }, }, ], diff --git a/plugins/template-function-response/package.json b/plugins/template-function-response/package.json index a59f8b47..b0453d0e 100644 --- a/plugins/template-function-response/package.json +++ b/plugins/template-function-response/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "jsonpath-plus": "^10.3.0", diff --git a/plugins/template-function-response/src/index.ts b/plugins/template-function-response/src/index.ts index 0e8c8909..ff44c932 100644 --- a/plugins/template-function-response/src/index.ts +++ b/plugins/template-function-response/src/index.ts @@ -1,5 +1,5 @@ import { DOMParser } from '@xmldom/xmldom'; -import { +import type { CallTemplateFunctionArgs, Context, FormInput, @@ -47,14 +47,14 @@ export const plugin: PluginDefinition = { if (!args.values.request || !args.values.header) return null; const response = await getResponse(ctx, { - requestId: args.values.request, + requestId: String(args.values.request || ''), purpose: args.purpose, - behavior: args.values.behavior ?? null, + behavior: args.values.behavior ? String(args.values.behavior) : null, }); if (response == null) return null; const header = response.headers.find( - h => h.name.toLowerCase() === String(args.values.header ?? '').toLowerCase(), + (h) => h.name.toLowerCase() === String(args.values.header ?? '').toLowerCase(), ); return header?.value ?? null; }, @@ -77,9 +77,9 @@ export const plugin: PluginDefinition = { if (!args.values.request || !args.values.path) return null; const response = await getResponse(ctx, { - requestId: args.values.request, + requestId: String(args.values.request || ''), purpose: args.purpose, - behavior: args.values.behavior ?? null, + behavior: args.values.behavior ? String(args.values.behavior) : null, }); if (response == null) return null; @@ -90,19 +90,19 @@ export const plugin: PluginDefinition = { let body; try { body = readFileSync(response.bodyPath, 'utf-8'); - } catch (_) { + } catch { return null; } try { - return filterJSONPath(body, args.values.path); - } catch (err) { + return filterJSONPath(body, String(args.values.path || '')); + } catch { // Probably not JSON, try XPath } try { - return filterXPath(body, args.values.path); - } catch (err) { + return filterXPath(body, String(args.values.path || '')); + } catch { // Probably not XML } @@ -113,17 +113,14 @@ export const plugin: PluginDefinition = { name: 'response.body.raw', description: 'Access the entire response body, as text', aliases: ['response'], - args: [ - requestArg, - behaviorArg, - ], + args: [requestArg, behaviorArg], async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise { if (!args.values.request) return null; const response = await getResponse(ctx, { - requestId: args.values.request, + requestId: String(args.values.request || ''), purpose: args.purpose, - behavior: args.values.behavior ?? null, + behavior: args.values.behavior ? String(args.values.behavior) : null, }); if (response == null) return null; @@ -134,7 +131,7 @@ export const plugin: PluginDefinition = { let body; try { body = readFileSync(response.bodyPath, 'utf-8'); - } catch (_) { + } catch { return null; } @@ -173,11 +170,18 @@ function filterXPath(body: string, path: string): string { } } -async function getResponse(ctx: Context, { requestId, behavior, purpose }: { - requestId: string, - behavior: string | null, - purpose: RenderPurpose, -}): Promise { +async function getResponse( + ctx: Context, + { + requestId, + behavior, + purpose, + }: { + requestId: string; + behavior: string | null; + purpose: RenderPurpose; + }, +): Promise { if (!requestId) return null; const httpRequest = await ctx.httpRequest.getById({ id: requestId ?? 'n/a' }); @@ -195,9 +199,7 @@ async function getResponse(ctx: Context, { requestId, behavior, purpose }: { // Previews happen a ton, and we don't want to send too many times on "always," so treat // it as "smart" during preview. - let finalBehavior = (behavior === 'always' && purpose === 'preview') - ? 'smart' - : behavior; + const finalBehavior = behavior === 'always' && purpose === 'preview' ? 'smart' : behavior; // Send if no responses and "smart," or "always" if ((finalBehavior === 'smart' && response == null) || finalBehavior === 'always') { diff --git a/plugins/template-function-uuid/package.json b/plugins/template-function-uuid/package.json index 23dbc421..853e29b8 100644 --- a/plugins/template-function-uuid/package.json +++ b/plugins/template-function-uuid/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "uuid": "^11.1.0" diff --git a/plugins/template-function-xml/package.json b/plugins/template-function-xml/package.json index dc1b6d8b..f10e1794 100755 --- a/plugins/template-function-xml/package.json +++ b/plugins/template-function-xml/package.json @@ -4,7 +4,8 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" }, "dependencies": { "@xmldom/xmldom": "^0.8.10", diff --git a/plugins/themes-yaak/package.json b/plugins/themes-yaak/package.json index 0de0788d..43ce69d3 100644 --- a/plugins/themes-yaak/package.json +++ b/plugins/themes-yaak/package.json @@ -4,6 +4,7 @@ "version": "0.0.1", "scripts": { "build": "yaakcli build ./src/index.ts", - "dev": "yaakcli dev ./src/index.js" + "dev": "yaakcli dev ./src/index.js", + "lint": "tsc --noEmit" } } diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 28b9f5ae..45ed79e7 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -36,7 +36,8 @@ use yaak_models::models::{ use yaak_models::query_manager::QueryManagerExt; use yaak_models::util::{BatchUpsertResult, UpdateSource, get_workspace_export_resources}; use yaak_plugins::events::{ - CallHttpRequestActionRequest, FilterResponse, GetHttpAuthenticationConfigResponse, + CallGrpcRequestActionRequest, CallHttpRequestActionRequest, FilterResponse, + GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, InternalEvent, InternalEventPayload, JsonPrimitive, PluginWindowContext, RenderPurpose, @@ -791,6 +792,14 @@ async fn cmd_http_request_actions( Ok(plugin_manager.get_http_request_actions(&window).await?) } +#[tauri::command] +async fn cmd_grpc_request_actions( + window: WebviewWindow, + plugin_manager: State<'_, PluginManager>, +) -> YaakResult> { + Ok(plugin_manager.get_grpc_request_actions(&window).await?) +} + #[tauri::command] async fn cmd_template_functions( window: WebviewWindow, @@ -830,6 +839,15 @@ async fn cmd_call_http_request_action( Ok(plugin_manager.call_http_request_action(&window, req).await?) } +#[tauri::command] +async fn cmd_call_grpc_request_action( + window: WebviewWindow, + req: CallGrpcRequestActionRequest, + plugin_manager: State<'_, PluginManager>, +) -> YaakResult<()> { + Ok(plugin_manager.call_grpc_request_action(&window, req).await?) +} + #[tauri::command] async fn cmd_call_http_authentication_action( window: WebviewWindow, @@ -1220,6 +1238,7 @@ pub fn run() { .invoke_handler(tauri::generate_handler![ cmd_call_http_authentication_action, cmd_call_http_request_action, + cmd_call_grpc_request_action, cmd_check_for_updates, cmd_create_grpc_request, cmd_curl_to_request, @@ -1236,6 +1255,7 @@ pub fn run() { cmd_get_workspace_meta, cmd_grpc_go, cmd_grpc_reflect, + cmd_grpc_request_actions, cmd_http_request_actions, cmd_import_data, cmd_install_plugin, diff --git a/src-tauri/src/plugin_events.rs b/src-tauri/src/plugin_events.rs index bcb136c4..d77c6516 100644 --- a/src-tauri/src/plugin_events.rs +++ b/src-tauri/src/plugin_events.rs @@ -1,6 +1,6 @@ use crate::http_request::send_http_request; -use crate::render::{render_http_request, render_json_value}; -use crate::window::{CreateWindowConfig, create_window}; +use crate::render::{render_grpc_request, render_http_request, render_json_value}; +use crate::window::{create_window, CreateWindowConfig}; use crate::{ call_frontend, cookie_jar_from_window, environment_from_window, get_window_from_window_context, workspace_from_window, @@ -13,13 +13,7 @@ use tauri_plugin_clipboard_manager::ClipboardExt; use yaak_models::models::{HttpResponse, Plugin}; use yaak_models::query_manager::QueryManagerExt; use yaak_models::util::UpdateSource; -use yaak_plugins::events::{ - Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse, GetCookieValueResponse, - GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload, - ListCookieNamesResponse, PluginWindowContext, RenderHttpRequestResponse, - SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, TemplateRenderResponse, - WindowNavigateEvent, -}; +use yaak_plugins::events::{Color, DeleteKeyValueResponse, EmptyPayload, FindHttpResponsesResponse, GetCookieValueResponse, GetHttpRequestByIdResponse, GetKeyValueResponse, Icon, InternalEvent, InternalEventPayload, ListCookieNamesResponse, PluginWindowContext, RenderGrpcRequestResponse, RenderHttpRequestResponse, SendHttpRequestResponse, SetKeyValueResponse, ShowToastRequest, TemplateRenderResponse, WindowNavigateEvent}; use yaak_plugins::manager::PluginManager; use yaak_plugins::plugin_handle::PluginHandle; use yaak_plugins::template_callback::PluginTemplateCallback; @@ -68,6 +62,30 @@ pub(crate) async fn handle_plugin_event( http_request, })) } + InternalEventPayload::RenderGrpcRequestRequest(req) => { + let window = get_window_from_window_context(app_handle, &window_context) + .expect("Failed to find window for render grpc request"); + + let workspace = + workspace_from_window(&window).expect("Failed to get workspace_id from window URL"); + let environment = environment_from_window(&window); + let base_environment = app_handle + .db() + .get_base_environment(&workspace.id) + .expect("Failed to get base environment"); + let cb = PluginTemplateCallback::new(app_handle, &window_context, req.purpose); + let grpc_request = render_grpc_request( + &req.grpc_request, + &base_environment, + environment.as_ref(), + &cb, + ) + .await + .expect("Failed to render grpc request"); + Some(InternalEventPayload::RenderGrpcRequestResponse(RenderGrpcRequestResponse { + grpc_request, + })) + } InternalEventPayload::RenderHttpRequestRequest(req) => { let window = get_window_from_window_context(app_handle, &window_context) .expect("Failed to find window for render http request"); diff --git a/src-tauri/yaak-grpc/src/reflection.rs b/src-tauri/yaak-grpc/src/reflection.rs index 57c827b6..b607c981 100644 --- a/src-tauri/yaak-grpc/src/reflection.rs +++ b/src-tauri/yaak-grpc/src/reflection.rs @@ -47,9 +47,14 @@ pub async fn fill_pool_from_files( ]; for p in paths { - if p.as_path().exists() { + if !p.exists() { + continue; + } + + // Dirs are added as includes + if p.is_dir() { + args.push("-I".to_string()); args.push(p.to_string_lossy().to_string()); - } else { continue; } @@ -62,6 +67,8 @@ pub async fn fill_pool_from_files( } else { debug!("ignoring {:?} since it does not exist.", parent) } + + args.push(p.to_string_lossy().to_string()); } let out = app_handle diff --git a/src-tauri/yaak-plugins/bindings/gen_events.ts b/src-tauri/yaak-plugins/bindings/gen_events.ts index 4ae63a3c..db224e39 100644 --- a/src-tauri/yaak-plugins/bindings/gen_events.ts +++ b/src-tauri/yaak-plugins/bindings/gen_events.ts @@ -6,6 +6,10 @@ export type BootRequest = { dir: string, watch: boolean, }; export type BootResponse = { name: string, version: string, }; +export type CallGrpcRequestActionArgs = { grpcRequest: GrpcRequest, protoFiles: Array, }; + +export type CallGrpcRequestActionRequest = { index: number, pluginRefId: string, args: CallGrpcRequestActionArgs, }; + export type CallHttpAuthenticationActionArgs = { contextId: string, values: { [key in string]?: JsonPrimitive }, }; export type CallHttpAuthenticationActionRequest = { index: number, pluginRefId: string, args: CallHttpAuthenticationActionArgs, }; @@ -336,14 +340,14 @@ export type GetCookieValueRequest = { name: string, }; export type GetCookieValueResponse = { value: string | null, }; +export type GetGrpcRequestActionsResponse = { actions: Array, pluginRefId: string, }; + export type GetHttpAuthenticationConfigRequest = { contextId: string, values: { [key in string]?: JsonPrimitive }, }; export type GetHttpAuthenticationConfigResponse = { args: Array, pluginRefId: string, actions?: Array, }; export type GetHttpAuthenticationSummaryResponse = { name: string, label: string, shortLabel: string, }; -export type GetHttpRequestActionsRequest = Record; - export type GetHttpRequestActionsResponse = { actions: Array, pluginRefId: string, }; export type GetHttpRequestByIdRequest = { id: string, }; @@ -360,6 +364,8 @@ export type GetThemesRequest = Record; export type GetThemesResponse = { themes: Array, }; +export type GrpcRequestAction = { label: string, icon?: Icon, }; + export type HttpAuthenticationAction = { label: string, icon?: Icon, }; export type HttpHeader = { name: string, value: string, }; @@ -376,7 +382,7 @@ export type ImportResponse = { resources: ImportResources, }; export type InternalEvent = { id: string, pluginRefId: string, pluginName: string, replyId: string | null, windowContext: PluginWindowContext, payload: InternalEventPayload, }; -export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse; +export type InternalEventPayload = { "type": "boot_request" } & BootRequest | { "type": "boot_response" } & BootResponse | { "type": "reload_request" } & EmptyPayload | { "type": "reload_response" } & BootResponse | { "type": "terminate_request" } | { "type": "terminate_response" } | { "type": "import_request" } & ImportRequest | { "type": "import_response" } & ImportResponse | { "type": "filter_request" } & FilterRequest | { "type": "filter_response" } & FilterResponse | { "type": "export_http_request_request" } & ExportHttpRequestRequest | { "type": "export_http_request_response" } & ExportHttpRequestResponse | { "type": "send_http_request_request" } & SendHttpRequestRequest | { "type": "send_http_request_response" } & SendHttpRequestResponse | { "type": "list_cookie_names_request" } & ListCookieNamesRequest | { "type": "list_cookie_names_response" } & ListCookieNamesResponse | { "type": "get_cookie_value_request" } & GetCookieValueRequest | { "type": "get_cookie_value_response" } & GetCookieValueResponse | { "type": "get_http_request_actions_request" } & EmptyPayload | { "type": "get_http_request_actions_response" } & GetHttpRequestActionsResponse | { "type": "call_http_request_action_request" } & CallHttpRequestActionRequest | { "type": "get_grpc_request_actions_request" } & EmptyPayload | { "type": "get_grpc_request_actions_response" } & GetGrpcRequestActionsResponse | { "type": "call_grpc_request_action_request" } & CallGrpcRequestActionRequest | { "type": "get_template_functions_request" } | { "type": "get_template_functions_response" } & GetTemplateFunctionsResponse | { "type": "call_template_function_request" } & CallTemplateFunctionRequest | { "type": "call_template_function_response" } & CallTemplateFunctionResponse | { "type": "get_http_authentication_summary_request" } & EmptyPayload | { "type": "get_http_authentication_summary_response" } & GetHttpAuthenticationSummaryResponse | { "type": "get_http_authentication_config_request" } & GetHttpAuthenticationConfigRequest | { "type": "get_http_authentication_config_response" } & GetHttpAuthenticationConfigResponse | { "type": "call_http_authentication_request" } & CallHttpAuthenticationRequest | { "type": "call_http_authentication_response" } & CallHttpAuthenticationResponse | { "type": "call_http_authentication_action_request" } & CallHttpAuthenticationActionRequest | { "type": "call_http_authentication_action_response" } & EmptyPayload | { "type": "copy_text_request" } & CopyTextRequest | { "type": "copy_text_response" } & EmptyPayload | { "type": "render_http_request_request" } & RenderHttpRequestRequest | { "type": "render_http_request_response" } & RenderHttpRequestResponse | { "type": "render_grpc_request_request" } & RenderGrpcRequestRequest | { "type": "render_grpc_request_response" } & RenderGrpcRequestResponse | { "type": "get_key_value_request" } & GetKeyValueRequest | { "type": "get_key_value_response" } & GetKeyValueResponse | { "type": "set_key_value_request" } & SetKeyValueRequest | { "type": "set_key_value_response" } & SetKeyValueResponse | { "type": "delete_key_value_request" } & DeleteKeyValueRequest | { "type": "delete_key_value_response" } & DeleteKeyValueResponse | { "type": "open_window_request" } & OpenWindowRequest | { "type": "window_navigate_event" } & WindowNavigateEvent | { "type": "window_close_event" } | { "type": "close_window_request" } & CloseWindowRequest | { "type": "template_render_request" } & TemplateRenderRequest | { "type": "template_render_response" } & TemplateRenderResponse | { "type": "show_toast_request" } & ShowToastRequest | { "type": "show_toast_response" } & EmptyPayload | { "type": "prompt_text_request" } & PromptTextRequest | { "type": "prompt_text_response" } & PromptTextResponse | { "type": "get_http_request_by_id_request" } & GetHttpRequestByIdRequest | { "type": "get_http_request_by_id_response" } & GetHttpRequestByIdResponse | { "type": "find_http_responses_request" } & FindHttpResponsesRequest | { "type": "find_http_responses_response" } & FindHttpResponsesResponse | { "type": "get_themes_request" } & GetThemesRequest | { "type": "get_themes_response" } & GetThemesResponse | { "type": "empty_response" } & EmptyPayload | { "type": "error_response" } & ErrorResponse; export type JsonPrimitive = string | number | boolean | null; @@ -408,6 +414,10 @@ required?: boolean, }; export type PromptTextResponse = { value: string | null, }; +export type RenderGrpcRequestRequest = { grpcRequest: GrpcRequest, purpose: RenderPurpose, }; + +export type RenderGrpcRequestResponse = { grpcRequest: GrpcRequest, }; + export type RenderHttpRequestRequest = { httpRequest: HttpRequest, purpose: RenderPurpose, }; export type RenderHttpRequestResponse = { httpRequest: HttpRequest, }; diff --git a/src-tauri/yaak-plugins/src/events.rs b/src-tauri/yaak-plugins/src/events.rs index 872691b7..a747a5b2 100644 --- a/src-tauri/yaak-plugins/src/events.rs +++ b/src-tauri/yaak-plugins/src/events.rs @@ -89,11 +89,16 @@ pub enum InternalEventPayload { GetCookieValueRequest(GetCookieValueRequest), GetCookieValueResponse(GetCookieValueResponse), - // Request Actions + // HTTP Request Actions GetHttpRequestActionsRequest(EmptyPayload), GetHttpRequestActionsResponse(GetHttpRequestActionsResponse), CallHttpRequestActionRequest(CallHttpRequestActionRequest), + // Grpc Request Actions + GetGrpcRequestActionsRequest(EmptyPayload), + GetGrpcRequestActionsResponse(GetGrpcRequestActionsResponse), + CallGrpcRequestActionRequest(CallGrpcRequestActionRequest), + // Template Functions GetTemplateFunctionsRequest, GetTemplateFunctionsResponse(GetTemplateFunctionsResponse), @@ -116,6 +121,9 @@ pub enum InternalEventPayload { RenderHttpRequestRequest(RenderHttpRequestRequest), RenderHttpRequestResponse(RenderHttpRequestResponse), + RenderGrpcRequestRequest(RenderGrpcRequestRequest), + RenderGrpcRequestResponse(RenderGrpcRequestResponse), + GetKeyValueRequest(GetKeyValueRequest), GetKeyValueResponse(GetKeyValueResponse), SetKeyValueRequest(SetKeyValueRequest), @@ -287,6 +295,21 @@ pub struct RenderHttpRequestResponse { pub http_request: HttpRequest, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct RenderGrpcRequestRequest { + pub grpc_request: GrpcRequest, + pub purpose: RenderPurpose, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct RenderGrpcRequestResponse { + pub grpc_request: GrpcRequest, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_events.ts")] @@ -967,11 +990,6 @@ impl Default for RenderPurpose { } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] -#[serde(default)] -#[ts(export, export_to = "gen_events.ts")] -pub struct GetHttpRequestActionsRequest {} - #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_events.ts")] @@ -1005,6 +1023,40 @@ pub struct CallHttpRequestActionArgs { pub http_request: HttpRequest, } +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct GetGrpcRequestActionsResponse { + pub actions: Vec, + pub plugin_ref_id: String, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct GrpcRequestAction { + pub label: String, + #[ts(optional)] + pub icon: Option, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct CallGrpcRequestActionRequest { + pub index: i32, + pub plugin_ref_id: String, + pub args: CallGrpcRequestActionArgs, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] +#[serde(default, rename_all = "camelCase")] +#[ts(export, export_to = "gen_events.ts")] +pub struct CallGrpcRequestActionArgs { + pub grpc_request: GrpcRequest, + pub proto_files: Vec, +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, TS)] #[serde(default, rename_all = "camelCase")] #[ts(export, export_to = "gen_events.ts")] diff --git a/src-tauri/yaak-plugins/src/manager.rs b/src-tauri/yaak-plugins/src/manager.rs index 92089928..fb2e9184 100644 --- a/src-tauri/yaak-plugins/src/manager.rs +++ b/src-tauri/yaak-plugins/src/manager.rs @@ -3,10 +3,11 @@ use crate::error::Error::{ }; use crate::error::Result; use crate::events::{ - BootRequest, CallHttpAuthenticationActionArgs, CallHttpAuthenticationActionRequest, - CallHttpAuthenticationRequest, CallHttpAuthenticationResponse, CallHttpRequestActionRequest, - CallTemplateFunctionArgs, CallTemplateFunctionRequest, CallTemplateFunctionResponse, - EmptyPayload, FilterRequest, FilterResponse, GetHttpAuthenticationConfigRequest, + BootRequest, CallGrpcRequestActionRequest, CallHttpAuthenticationActionArgs, + CallHttpAuthenticationActionRequest, CallHttpAuthenticationRequest, + CallHttpAuthenticationResponse, CallHttpRequestActionRequest, CallTemplateFunctionArgs, + CallTemplateFunctionRequest, CallTemplateFunctionResponse, EmptyPayload, FilterRequest, + FilterResponse, GetGrpcRequestActionsResponse, GetHttpAuthenticationConfigRequest, GetHttpAuthenticationConfigResponse, GetHttpAuthenticationSummaryResponse, GetHttpRequestActionsResponse, GetTemplateFunctionsResponse, GetThemesRequest, GetThemesResponse, ImportRequest, ImportResponse, InternalEvent, InternalEventPayload, @@ -426,6 +427,27 @@ impl PluginManager { Ok(themes) } + pub async fn get_grpc_request_actions( + &self, + window: &WebviewWindow, + ) -> Result> { + let reply_events = self + .send_and_wait( + &PluginWindowContext::new(window), + &InternalEventPayload::GetGrpcRequestActionsRequest(EmptyPayload {}), + ) + .await?; + + let mut all_actions = Vec::new(); + for event in reply_events { + if let InternalEventPayload::GetGrpcRequestActionsResponse(resp) = event.payload { + all_actions.push(resp.clone()); + } + } + + Ok(all_actions) + } + pub async fn get_http_request_actions( &self, window: &WebviewWindow, @@ -495,6 +517,23 @@ impl PluginManager { Ok(()) } + pub async fn call_grpc_request_action( + &self, + window: &WebviewWindow, + req: CallGrpcRequestActionRequest, + ) -> Result<()> { + let ref_id = req.plugin_ref_id.clone(); + let plugin = + self.get_plugin_by_ref_id(ref_id.as_str()).await.ok_or(PluginNotFoundErr(ref_id))?; + let event = plugin.build_event_to_send( + &PluginWindowContext::new(window), + &InternalEventPayload::CallGrpcRequestActionRequest(req), + None, + ); + plugin.send(&event).await?; + Ok(()) + } + pub async fn get_http_authentication_summaries( &self, window: &WebviewWindow, diff --git a/src-web/components/GrpcProtoSelectionDialog.tsx b/src-web/components/GrpcProtoSelectionDialog.tsx index 2f2fc5d6..bd144df1 100644 --- a/src-web/components/GrpcProtoSelectionDialog.tsx +++ b/src-web/components/GrpcProtoSelectionDialog.tsx @@ -10,6 +10,7 @@ import { IconButton } from './core/IconButton'; import { InlineCode } from './core/InlineCode'; import { Link } from './core/Link'; import { HStack, VStack } from './core/Stacks'; +import { Icon } from './core/Icon'; interface Props { onDone: () => void; @@ -45,6 +46,7 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp + + {reflectError && ( + +

+ Reflection failed on URL {request.url || 'n/a'} +

+

{reflectError.trim()}

+
+ )} {!serverReflection && services != null && services.length > 0 && (

@@ -108,39 +135,41 @@ function GrpcProtoSelectionDialogWithRequest({ request }: Props & { request: Grp - - + - {protoFiles.map((f, i) => ( - - - - - ))} + {protoFiles.map((f, i) => { + const parts = f.split('/'); + return ( + + + + + + ); + })}
- Added Files - + + Added File Paths
{f.split('/').pop()} - { - await protoFilesKv.set(protoFiles.filter((p) => p !== f)); - }} - /> -
+ + + {parts.length > 3 && '.../'} + {parts.slice(-3).join('/')} + + { + await protoFilesKv.set(protoFiles.filter((p) => p !== f)); + }} + /> +
)} - {reflectError && ( - -

- Reflection failed on URL {request.url || 'n/a'} -

- {reflectError} -
- )} {reflectionUnimplemented && protoFiles.length === 0 && ( {request.url} doesn't implement{' '} diff --git a/src-web/components/core/Banner.tsx b/src-web/components/core/Banner.tsx index 4012d1bb..33d19950 100644 --- a/src-web/components/core/Banner.tsx +++ b/src-web/components/core/Banner.tsx @@ -14,7 +14,6 @@ export function Banner({ children, className, color }: BannerProps) { className={classNames( className, `x-theme-banner--${color}`, - 'whitespace-pre-wrap', 'border border-border bg-surface', 'px-4 py-3 rounded-lg select-auto', 'overflow-auto text-text', diff --git a/src-web/components/core/Icon.tsx b/src-web/components/core/Icon.tsx index c151a5fe..a1053973 100644 --- a/src-web/components/core/Icon.tsx +++ b/src-web/components/core/Icon.tsx @@ -45,6 +45,7 @@ const icons = { eye: lucide.EyeIcon, eye_closed: lucide.EyeOffIcon, file_code: lucide.FileCodeIcon, + folder_code: lucide.FolderCodeIcon, filter: lucide.FilterIcon, flame: lucide.FlameIcon, flask: lucide.FlaskConicalIcon, @@ -134,6 +135,8 @@ export const Icon = memo(function Icon({ title={title} className={classNames( className, + !spin && 'transform-cpu', + spin && 'animate-spin', 'flex-shrink-0', size === 'xl' && 'h-6 w-6', size === 'lg' && 'h-5 w-5', @@ -149,7 +152,6 @@ export const Icon = memo(function Icon({ color === 'success' && 'text-success', color === 'primary' && 'text-primary', color === 'secondary' && 'text-secondary', - spin && 'animate-spin', )} /> ); diff --git a/src-web/components/sidebar/SidebarItemContextMenu.tsx b/src-web/components/sidebar/SidebarItemContextMenu.tsx index ee6a8f3c..9a2498c0 100644 --- a/src-web/components/sidebar/SidebarItemContextMenu.tsx +++ b/src-web/components/sidebar/SidebarItemContextMenu.tsx @@ -3,6 +3,7 @@ import { useAtomValue } from 'jotai'; import React, { useMemo } from 'react'; import { openFolderSettings } from '../../commands/openFolderSettings'; import { useCreateDropdownItems } from '../../hooks/useCreateDropdownItems'; +import { useGrpcRequestActions } from '../../hooks/useGrpcRequestActions'; import { useHttpRequestActions } from '../../hooks/useHttpRequestActions'; import { useMoveToWorkspace } from '../../hooks/useMoveToWorkspace'; import { useSendAnyHttpRequest } from '../../hooks/useSendAnyHttpRequest'; @@ -25,6 +26,7 @@ interface Props { export function SidebarItemContextMenu({ child, show, close }: Props) { const sendManyRequests = useSendManyRequests(); const httpRequestActions = useHttpRequestActions(); + const grpcRequestActions = useGrpcRequestActions(); const sendRequest = useSendAnyHttpRequest(); const workspaces = useAtomValue(workspacesAtom); const moveToWorkspace = useMoveToWorkspace(child.id); @@ -65,25 +67,35 @@ export function SidebarItemContextMenu({ child, show, close }: Props) { const requestItems: DropdownItem[] = child.model === 'http_request' ? [ - { - label: 'Send', - hotKeyAction: 'http_request.send', - hotKeyLabelOnly: true, // Already bound in URL bar - leftSlot: , - onSelect: () => sendRequest.mutate(child.id), + { + label: 'Send', + hotKeyAction: 'http_request.send', + hotKeyLabelOnly: true, // Already bound in URL bar + leftSlot: , + onSelect: () => sendRequest.mutate(child.id), + }, + ...httpRequestActions.map((a) => ({ + label: a.label, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leftSlot: , + onSelect: async () => { + const request = getModel('http_request', child.id); + if (request != null) await a.call(request); }, - ...httpRequestActions.map((a) => ({ - label: a.label, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - leftSlot: , - onSelect: async () => { - const request = getModel('http_request', child.id); - if (request != null) await a.call(request); - }, - })), - { type: 'separator' }, - ] - : []; + })), + { type: 'separator' }, + ] + : child.model === 'grpc_request' + ? grpcRequestActions.map((a) => ({ + label: a.label, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + leftSlot: , + onSelect: async () => { + const request = getModel('grpc_request', child.id); + if (request != null) await a.call(request); + }, + })) + : []; return [ ...requestItems, { @@ -134,6 +146,7 @@ export function SidebarItemContextMenu({ child, show, close }: Props) { child.model, createDropdownItems, httpRequestActions, + grpcRequestActions, moveToWorkspace.mutate, sendManyRequests, sendRequest, diff --git a/src-web/hooks/useGrpcProtoFiles.ts b/src-web/hooks/useGrpcProtoFiles.ts index c0f90e56..fa7a6022 100644 --- a/src-web/hooks/useGrpcProtoFiles.ts +++ b/src-web/hooks/useGrpcProtoFiles.ts @@ -1,3 +1,4 @@ +import { getKeyValue } from '../lib/keyValueStore'; import { useKeyValue } from './useKeyValue'; export function protoFilesArgs(requestId: string | null) { @@ -10,3 +11,7 @@ export function protoFilesArgs(requestId: string | null) { export function useGrpcProtoFiles(activeRequestId: string | null) { return useKeyValue({ ...protoFilesArgs(activeRequestId), fallback: [] }); } + +export async function getGrpcProtoFiles(activeRequestId: string | null) { + return getKeyValue({ ...protoFilesArgs(activeRequestId), fallback: [] }); +} diff --git a/src-web/hooks/useGrpcRequestActions.ts b/src-web/hooks/useGrpcRequestActions.ts new file mode 100644 index 00000000..f1020e10 --- /dev/null +++ b/src-web/hooks/useGrpcRequestActions.ts @@ -0,0 +1,51 @@ +import { useQuery } from '@tanstack/react-query'; +import type { GrpcRequest } from '@yaakapp-internal/models'; +import type { + CallGrpcRequestActionRequest, + GetGrpcRequestActionsResponse, + GrpcRequestAction, +} from '@yaakapp-internal/plugins'; +import { useMemo } from 'react'; +import { invokeCmd } from '../lib/tauri'; +import { getGrpcProtoFiles } from './useGrpcProtoFiles'; +import { usePluginsKey } from './usePlugins'; + +export type CallableGrpcRequestAction = Pick & { + call: (grpcRequest: GrpcRequest) => Promise; +}; + +export function useGrpcRequestActions() { + const pluginsKey = usePluginsKey(); + + const actionsResult = useQuery({ + queryKey: ['grpc_request_actions', pluginsKey], + queryFn: async () => { + const responses = await invokeCmd( + 'cmd_grpc_request_actions', + ); + + return responses.flatMap((r) => + r.actions.map((a, i) => ({ + label: a.label, + icon: a.icon, + call: async (grpcRequest: GrpcRequest) => { + const protoFiles = await getGrpcProtoFiles(grpcRequest.id); + const payload: CallGrpcRequestActionRequest = { + index: i, + pluginRefId: r.pluginRefId, + args: { grpcRequest, protoFiles }, + }; + await invokeCmd('cmd_call_grpc_request_action', { req: payload }); + }, + })), + ); + }, + }); + + const actions = useMemo(() => { + return actionsResult.data ?? []; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [JSON.stringify(actionsResult.data)]); + + return actions; +} diff --git a/src-web/lib/tauri.ts b/src-web/lib/tauri.ts index 18abbae1..8ee1a755 100644 --- a/src-web/lib/tauri.ts +++ b/src-web/lib/tauri.ts @@ -4,6 +4,7 @@ import { invoke } from '@tauri-apps/api/core'; type TauriCmd = | 'cmd_get_themes' | 'cmd_call_http_authentication_action' + | 'cmd_call_grpc_request_action' | 'cmd_call_http_request_action' | 'cmd_check_for_updates' | 'cmd_create_grpc_request' @@ -23,6 +24,7 @@ type TauriCmd = | 'cmd_get_workspace_meta' | 'cmd_grpc_go' | 'cmd_grpc_reflect' + | 'cmd_grpc_request_actions' | 'cmd_http_request_actions' | 'cmd_import_data' | 'cmd_install_plugin'