diff --git a/package-lock.json b/package-lock.json index d8709f2bd8241d9ba67012fd275e65f83ce52b1d..4a3dd69f9a9073ba5b5749b9a95cc024f56447be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "sortablejs": "^1.15.2", "vue": "~3.4.0", "vue-matomo": "^4.2.0", - "vue-router": "~4.3.0", + "vue-router": "~4.4.0", "vue3-cookies": "~1.0.0" }, "devDependencies": { @@ -55,7 +55,7 @@ "prettier": "~3.3.0", "rollup-plugin-node-polyfills": "~0.2.1", "sass": "^1.66.0", - "typescript": "~5.4.0", + "typescript": "~5.5.0", "vite": "~5.3.0", "vue-tsc": "~2.0.0" } @@ -2455,9 +2455,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz", - "integrity": "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw==", + "version": "20.14.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", + "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2488,16 +2488,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.13.1.tgz", - "integrity": "sha512-kZqi+WZQaZfPKnsflLJQCz6Ze9FFSMfXrrIOcyargekQxG37ES7DJNpJUE9Q/X5n3yTIP/WPutVNzgknQ7biLg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.14.1.tgz", + "integrity": "sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.13.1", - "@typescript-eslint/type-utils": "7.13.1", - "@typescript-eslint/utils": "7.13.1", - "@typescript-eslint/visitor-keys": "7.13.1", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/type-utils": "7.14.1", + "@typescript-eslint/utils": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2521,15 +2521,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.13.1.tgz", - "integrity": "sha512-1ELDPlnLvDQ5ybTSrMhRTFDfOQEOXNM+eP+3HT/Yq7ruWpciQw+Avi73pdEbA4SooCawEWo3dtYbF68gN7Ed1A==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.14.1.tgz", + "integrity": "sha512-8lKUOebNLcR0D7RvlcloOacTOWzOqemWEWkKSVpMZVF/XVcwjPR+3MD08QzbW9TCGJ+DwIc6zUSGZ9vd8cO1IA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.13.1", - "@typescript-eslint/types": "7.13.1", - "@typescript-eslint/typescript-estree": "7.13.1", - "@typescript-eslint/visitor-keys": "7.13.1", + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4" }, "engines": { @@ -2549,13 +2549,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.13.1.tgz", - "integrity": "sha512-adbXNVEs6GmbzaCpymHQ0MB6E4TqoiVbC0iqG3uijR8ZYfpAXMGttouQzF4Oat3P2GxDVIrg7bMI/P65LiQZdg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.14.1.tgz", + "integrity": "sha512-gPrFSsoYcsffYXTOZ+hT7fyJr95rdVe4kGVX1ps/dJ+DfmlnjFN/GcMxXcVkeHDKqsq6uAcVaQaIi3cFffmAbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.1", - "@typescript-eslint/visitor-keys": "7.13.1" + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2566,13 +2566,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.13.1.tgz", - "integrity": "sha512-aWDbLu1s9bmgPGXSzNCxELu+0+HQOapV/y+60gPXafR8e2g1Bifxzevaa+4L2ytCWm+CHqpELq4CSoN9ELiwCg==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.14.1.tgz", + "integrity": "sha512-/MzmgNd3nnbDbOi3LfasXWWe292+iuo+umJ0bCCMCPc1jLO/z2BQmWUUUXvXLbrQey/JgzdF/OV+I5bzEGwJkQ==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.13.1", - "@typescript-eslint/utils": "7.13.1", + "@typescript-eslint/typescript-estree": "7.14.1", + "@typescript-eslint/utils": "7.14.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2593,9 +2593,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.13.1.tgz", - "integrity": "sha512-7K7HMcSQIAND6RBL4kDl24sG/xKM13cA85dc7JnmQXw2cBDngg7c19B++JzvJHRG3zG36n9j1i451GBzRuHchw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.14.1.tgz", + "integrity": "sha512-mL7zNEOQybo5R3AavY+Am7KLv8BorIv7HCYS5rKoNZKQD9tsfGUpO4KdAn3sSUvTiS4PQkr2+K0KJbxj8H9NDg==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2606,13 +2606,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.13.1.tgz", - "integrity": "sha512-uxNr51CMV7npU1BxZzYjoVz9iyjckBduFBP0S5sLlh1tXYzHzgZ3BR9SVsNed+LmwKrmnqN3Kdl5t7eZ5TS1Yw==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.14.1.tgz", + "integrity": "sha512-k5d0VuxViE2ulIO6FbxxSZaxqDVUyMbXcidC8rHvii0I56XZPv8cq+EhMns+d/EVIL41sMXqRbK3D10Oza1bbA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.1", - "@typescript-eslint/visitor-keys": "7.13.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/visitor-keys": "7.14.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2634,15 +2634,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.13.1.tgz", - "integrity": "sha512-h5MzFBD5a/Gh/fvNdp9pTfqJAbuQC4sCN2WzuXme71lqFJsZtLbjxfSk4r3p02WIArOF9N94pdsLiGutpDbrXQ==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.14.1.tgz", + "integrity": "sha512-CMmVVELns3nak3cpJhZosDkm63n+DwBlDX8g0k4QUa9BMnF+lH2lr3d130M1Zt1xxmB3LLk3NV7KQCq86ZBBhQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.13.1", - "@typescript-eslint/types": "7.13.1", - "@typescript-eslint/typescript-estree": "7.13.1" + "@typescript-eslint/scope-manager": "7.14.1", + "@typescript-eslint/types": "7.14.1", + "@typescript-eslint/typescript-estree": "7.14.1" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -2656,12 +2656,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.13.1.tgz", - "integrity": "sha512-k/Bfne7lrP7hcb7m9zSsgcBmo+8eicqqfNAJ7uUY+jkTFpKeH2FSkWpFRtimBxgkyvqfu9jTPRbYOvud6isdXA==", + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.14.1.tgz", + "integrity": "sha512-Crb+F75U1JAEtBeQGxSKwI60hZmmzaqA3z9sYsVm8X7W5cwLEm5bRe0/uXS6+MR/y8CVpKSR/ontIAIEPFcEkA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.13.1", + "@typescript-eslint/types": "7.14.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2692,41 +2692,38 @@ } }, "node_modules/@volar/language-core": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.3.0.tgz", - "integrity": "sha512-pvhL24WUh3VDnv7Yw5N1sjhPtdx7q9g+Wl3tggmnkMcyK8GcCNElF2zHiKznryn0DiUGk+eez/p2qQhz+puuHw==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.3.4.tgz", + "integrity": "sha512-wXBhY11qG6pCDAqDnbBRFIDSIwbqkWI7no+lj5+L7IlA7HRIjRP7YQLGzT0LF4lS6eHkMSsclXqy9DwYJasZTQ==", "dev": true, "dependencies": { - "@volar/source-map": "2.3.0" + "@volar/source-map": "2.3.4" } }, "node_modules/@volar/source-map": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.3.0.tgz", - "integrity": "sha512-G/228aZjAOGhDjhlyZ++nDbKrS9uk+5DMaEstjvzglaAw7nqtDyhnQAsYzUg6BMP9BtwZ59RIw5HGePrutn00Q==", - "dev": true, - "dependencies": { - "muggle-string": "^0.4.0" - } + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.3.4.tgz", + "integrity": "sha512-C+t63nwcblqLIVTYXaVi/+gC8NukDaDIQI72J3R7aXGvtgaVB16c+J8Iz7/VfOy7kjYv7lf5GhBny6ACw9fTGQ==", + "dev": true }, "node_modules/@volar/typescript": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.3.0.tgz", - "integrity": "sha512-PtUwMM87WsKVeLJN33GSTUjBexlKfKgouWlOUIv7pjrOnTwhXHZNSmpc312xgXdTjQPpToK6KXSIcKu9sBQ5LQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.3.4.tgz", + "integrity": "sha512-acCvt7dZECyKcvO5geNybmrqOsu9u8n5XP1rfiYsOLYGPxvHRav9BVmEdRyZ3vvY6mNyQ1wLL5Hday4IShe17w==", "dev": true, "dependencies": { - "@volar/language-core": "2.3.0", + "@volar/language-core": "2.3.4", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", - "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.30.tgz", + "integrity": "sha512-ZL8y4Xxdh8O6PSwfdZ1IpQ24PjTAieOz3jXb/MDTfDtANcKBMxg1KLm6OX2jofsaQGYfIVzd3BAG22i56/cF1w==", "dependencies": { "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.29", + "@vue/shared": "3.4.30", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" @@ -2738,24 +2735,24 @@ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, "node_modules/@vue/compiler-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", - "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.30.tgz", + "integrity": "sha512-+16Sd8lYr5j/owCbr9dowcNfrHd+pz+w2/b5Lt26Oz/kB90C9yNbxQ3bYOvt7rI2bxk0nqda39hVcwDFw85c2Q==", "dependencies": { - "@vue/compiler-core": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-core": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", - "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.30.tgz", + "integrity": "sha512-8vElKklHn/UY8+FgUFlQrYAPbtiSB2zcgeRKW7HkpSRn/JjMRmZvuOtwDx036D1aqKNSTtXkWRfqx53Qb+HmMg==", "dependencies": { "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.29", - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29", + "@vue/compiler-core": "3.4.30", + "@vue/compiler-dom": "3.4.30", + "@vue/compiler-ssr": "3.4.30", + "@vue/shared": "3.4.30", "estree-walker": "^2.0.2", "magic-string": "^0.30.10", "postcss": "^8.4.38", @@ -2776,12 +2773,12 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", - "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.30.tgz", + "integrity": "sha512-ZJ56YZGXJDd6jky4mmM0rNaNP6kIbQu9LTKZDhcpddGe/3QIalB1WHHmZ6iZfFNyj5mSypTa4+qDJa5VIuxMSg==", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-dom": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/devtools-api": { @@ -2828,16 +2825,17 @@ } }, "node_modules/@vue/language-core": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.21.tgz", - "integrity": "sha512-vjs6KwnCK++kIXT+eI63BGpJHfHNVJcUCr3RnvJsccT3vbJnZV5IhHR2puEkoOkIbDdp0Gqi1wEnv3hEd3WsxQ==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.0.22.tgz", + "integrity": "sha512-dNTAAtEOuMiz7N1s5tKpypnVVCtawxVSF5BukD0ELcYSw+DSbrSlYYSw8GuwvurodCeYFSHsmslE+c2sYDNoiA==", "dev": true, "dependencies": { - "@volar/language-core": "~2.3.0-alpha.15", + "@volar/language-core": "~2.3.1", "@vue/compiler-dom": "^3.4.0", "@vue/shared": "^3.4.0", "computeds": "^0.0.1", "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", "path-browserify": "^1.0.1", "vue-template-compiler": "^2.7.14" }, @@ -2851,49 +2849,49 @@ } }, "node_modules/@vue/reactivity": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", - "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.30.tgz", + "integrity": "sha512-bVJurnCe3LS0JII8PPoAA63Zd2MBzcKrEzwdQl92eHCcxtIbxD2fhNwJpa+KkM3Y/A4T5FUnmdhgKwOf6BfbcA==", "dependencies": { - "@vue/shared": "3.4.29" + "@vue/shared": "3.4.30" } }, "node_modules/@vue/runtime-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", - "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.30.tgz", + "integrity": "sha512-qaFEbnNpGz+tlnkaualomogzN8vBLkgzK55uuWjYXbYn039eOBZrWxyXWq/7qh9Bz2FPifZqGjVDl/FXiq9L2g==", "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/reactivity": "3.4.30", + "@vue/shared": "3.4.30" } }, "node_modules/@vue/runtime-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", - "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.30.tgz", + "integrity": "sha512-tV6B4YiZRj5QsaJgw2THCy5C1H+2UeywO9tqgWEc21tn85qHEERndHN/CxlyXvSBFrpmlexCIdnqPuR9RM9thw==", "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/runtime-core": "3.4.29", - "@vue/shared": "3.4.29", + "@vue/reactivity": "3.4.30", + "@vue/runtime-core": "3.4.30", + "@vue/shared": "3.4.30", "csstype": "^3.1.3" } }, "node_modules/@vue/server-renderer": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", - "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.30.tgz", + "integrity": "sha512-TBD3eqR1DeDc0cMrXS/vEs/PWzq1uXxnvjoqQuDGFIEHFIwuDTX/KWAQKIBjyMWLFHEeTDGYVsYci85z2UbTDg==", "dependencies": { - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-ssr": "3.4.30", + "@vue/shared": "3.4.30" }, "peerDependencies": { - "vue": "3.4.29" + "vue": "3.4.30" } }, "node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==" + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.30.tgz", + "integrity": "sha512-CLg+f8RQCHQnKvuHY9adMsMaQOcqclh6Z5V9TaoMgy0ut0tz848joZ7/CYFFyF/yZ5i2yaw7Fn498C+CNZVHIg==" }, "node_modules/@vue/tsconfig": { "version": "0.5.1", @@ -4697,12 +4695,15 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", + "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5077,9 +5078,9 @@ } }, "node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -5375,10 +5376,13 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6529,9 +6533,9 @@ } }, "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", + "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", "devOptional": true, "bin": { "tsc": "bin/tsc", @@ -6681,15 +6685,15 @@ "dev": true }, "node_modules/vue": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", - "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", + "version": "3.4.30", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.30.tgz", + "integrity": "sha512-NcxtKCwkdf1zPsr7Y8+QlDBCGqxvjLXF2EX+yi76rV5rrz90Y6gK1cq0olIhdWGgrlhs9ElHuhi9t3+W5sG5Xw==", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-sfc": "3.4.29", - "@vue/runtime-dom": "3.4.29", - "@vue/server-renderer": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-dom": "3.4.30", + "@vue/compiler-sfc": "3.4.30", + "@vue/runtime-dom": "3.4.30", + "@vue/server-renderer": "3.4.30", + "@vue/shared": "3.4.30" }, "peerDependencies": { "typescript": "*" @@ -6734,9 +6738,9 @@ } }, "node_modules/vue-router": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.3.tgz", - "integrity": "sha512-8Q+u+WP4N2SXY38FDcF2H1dUEbYVHVPtPCPZj/GTZx8RCbiB8AtJP9+YIxn4Vs0svMTNQcLIzka4GH7Utkx9xQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.4.0.tgz", + "integrity": "sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==", "dependencies": { "@vue/devtools-api": "^6.5.1" }, @@ -6758,13 +6762,13 @@ } }, "node_modules/vue-tsc": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.21.tgz", - "integrity": "sha512-E6x1p1HaHES6Doy8pqtm7kQern79zRtIewkf9fiv7Y43Zo4AFDS5hKi+iHi2RwEhqRmuiwliB1LCEFEGwvxQnw==", + "version": "2.0.22", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.0.22.tgz", + "integrity": "sha512-lMBIwPBO0sxCcmvu45yt1b035AaQ8/XSXQDk8m75y4j0jSXY/y/XzfEtssQ9JMS47lDaR10O3/926oCs8OeGUw==", "dev": true, "dependencies": { - "@volar/typescript": "~2.3.0-alpha.15", - "@vue/language-core": "2.0.21", + "@volar/typescript": "~2.3.1", + "@vue/language-core": "2.0.22", "semver": "^7.5.4" }, "bin": { diff --git a/package.json b/package.json index 1d9479a6f5d65fd4741320d34c7233fe5a4f218d..ba9afc0e0c29b0ceea2315aeba1bf7716b6d3ae8 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "sortablejs": "^1.15.2", "vue": "~3.4.0", "vue-matomo": "^4.2.0", - "vue-router": "~4.3.0", + "vue-router": "~4.4.0", "vue3-cookies": "~1.0.0" }, "devDependencies": { @@ -59,7 +59,7 @@ "prettier": "~3.3.0", "rollup-plugin-node-polyfills": "~0.2.1", "sass": "^1.66.0", - "typescript": "~5.4.0", + "typescript": "~5.5.0", "vite": "~5.3.0", "vue-tsc": "~2.0.0" } diff --git a/src/client/models/DocumentationEnum.ts b/src/client/models/DocumentationEnum.ts index 9bcb475dbc709abaf07a5faa8067932a1bdafd12..a80f7b59a5f013f5dfa74412abb0c7023b0bd8c2 100644 --- a/src/client/models/DocumentationEnum.ts +++ b/src/client/models/DocumentationEnum.ts @@ -3,10 +3,10 @@ /* tslint:disable */ /* eslint-disable */ export enum DocumentationEnum { - USAGE = 'usage', - INPUT = 'input', - OUTPUT = 'output', - CHANGELOG = 'changelog', - PARAMETER_SCHEMA = 'parameter_schema', - CLOWM_INFO = 'clowm_info', + USAGE_MD = 'usage.md', + INPUT_MD = 'input.md', + OUTPUT_MD = 'output.md', + CHANGELOG_MD = 'changelog.md', + PARAMETER_SCHEMA_JSON = 'parameter_schema.json', + CLOWM_INFO_JSON = 'clowm_info.json', } diff --git a/src/components/workflows/modals/ArbitraryWorkflowModal.vue b/src/components/workflows/modals/ArbitraryWorkflowModal.vue index 749dcd9dab1dc075d5cc4a38aaf0e504f33e781b..3ac3acf3fe8979e7330bfab48afae7f0314c7e28 100644 --- a/src/components/workflows/modals/ArbitraryWorkflowModal.vue +++ b/src/components/workflows/modals/ArbitraryWorkflowModal.vue @@ -3,11 +3,7 @@ import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { computed, onMounted, reactive, ref, watch } from "vue"; import { useRouter } from "vue-router"; -import { - GitRepository, - requiredRepositoryFiles, - determineGitIcon, -} from "@/utils/GitRepository"; +import { GitRepository, determineGitIcon } from "@/utils/GitRepository"; import { Collapse, Modal } from "bootstrap"; import { useWorkflowStore } from "@/stores/workflows"; import { NextflowVersion, type WorkflowModeOut } from "@/client"; @@ -16,6 +12,7 @@ import { useS3KeyStore } from "@/stores/s3keys"; import { useResourceStore } from "@/stores/resources"; import { createDownloadUrl } from "@/utils/DownloadJson"; import dayjs from "dayjs"; +import { AxiosError } from "axios"; const props = defineProps<{ modalId: string; @@ -180,7 +177,7 @@ docker { -v /var/lib/lxcfs/proc/meminfo:/proc/meminfo:rw \\ -v /var/lib/lxcfs/proc/stat:/proc/stat:rw \\ -v /var/lib/lxcfs/proc/swaps:/proc/swaps:rw \\ - -v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw" + -v /var/lib/lxcfs/proc/uptime:/proc/uptime:rw" } // Disable unwanted features @@ -351,31 +348,52 @@ function checkRepository() { : undefined, ); repo - .checkFilesExist( - requiredRepositoryFiles( - workflowMode.modeEnabled ? [workflowMode.mode] : [], - ), - true, - ) - .then(() => { - formState.allowUpload = true; + .validateRepo(workflowMode.modeEnabled ? [workflowMode.mode] : []) + .then((missingFiles) => { + if (missingFiles.length === 0) { + formState.allowUpload = true; + } else { + formState.missingFiles = missingFiles; + } }) - .catch((e: Error) => { - try { - const headers = JSON.parse(e.message); - formState.ratelimit_reset = parseInt(headers["x-ratelimit-reset"]); - } catch { - formState.missingFiles = e.message.split(","); - // Allow execution of the workflow if main.nf and parameter schema are not missing - if ( - formState.missingFiles.findIndex( - (file) => file === "main.nf" || file === "nextflow_schema.json", - ) < 0 - ) { - formState.allowUpload = true; - } + .catch((e) => { + if ( + e instanceof AxiosError && + e.response != undefined && + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimit_reset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); } + console.error(e); }); + // repo + // .checkFilesExist( + // requiredRepositoryFiles( + // workflowMode.modeEnabled ? [workflowMode.mode] : [], + // ), + // true, + // ) + // .then(() => { + // formState.allowUpload = true; + // }) + // .catch((e: Error) => { + // try { + // const headers = JSON.parse(e.message); + // + // } catch { + // formState.missingFiles = e.message.split(","); + // // Allow execution of the workflow if main.nf and parameter schema are not missing + // if ( + // formState.missingFiles.findIndex( + // (file) => file === "main.nf" || file === "nextflow_schema.json", + // ) < 0 + // ) { + // formState.allowUpload = true; + // } + // } + // }); } catch (e) { formState.unsupportedRepository = true; workflowRepositoryElement.value?.setCustomValidity( diff --git a/src/components/workflows/modals/CreateWorkflowModal.vue b/src/components/workflows/modals/CreateWorkflowModal.vue index c1ec15a1573a77e820434c8c705cde6856b8b416..08065b446aa32d341b25d5e6df4df5a3250212af 100644 --- a/src/components/workflows/modals/CreateWorkflowModal.vue +++ b/src/components/workflows/modals/CreateWorkflowModal.vue @@ -10,16 +10,13 @@ import { import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { ApiError } from "@/client"; -import { - GitRepository, - requiredRepositoryFiles, - determineGitIcon, -} from "@/utils/GitRepository"; +import { GitRepository, determineGitIcon } from "@/utils/GitRepository"; import { valid } from "semver"; import WorkflowModeTransitionGroup from "@/components/transitions/WorkflowModeTransitionGroup.vue"; import { useWorkflowStore } from "@/stores/workflows"; import BootstrapToast from "@/components/BootstrapToast.vue"; import dayjs from "dayjs"; +import { AxiosError } from "axios"; const workflowRepository = useWorkflowStore(); // Emitted Events @@ -252,24 +249,29 @@ function checkRepository() { ? repositoryCredentials.token : undefined, ); - const requiredFiles = requiredRepositoryFiles( - workflowModes.hasModes ? workflowModes.modes : [], - ); repo - .checkFilesExist(requiredFiles, true) - .then(() => { - formState.allowUpload = true; - }) - .catch((e: Error) => { - try { - const headers = JSON.parse(e.message); - formState.ratelimit_reset = parseInt(headers["x-ratelimit-reset"]); - } catch { - formState.missingFiles = e.message.split(","); + .validateRepo(workflowModes.hasModes ? workflowModes.modes : []) + .then((missingFiles) => { + if (missingFiles.length === 0) { + formState.allowUpload = true; + } else { + formState.missingFiles = missingFiles; workflowGitCommitHashElement.value?.setCustomValidity( "Files are missing in the repository", ); } + }) + .catch((e) => { + if ( + e instanceof AxiosError && + e.response != undefined && + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimit_reset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } + console.error(e); }); } catch (e) { formState.unsupportedRepository = true; diff --git a/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue b/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue index e49fbc3081cbf310b2ea94e82a6c1da400f15c30..3aed90940da35b65d51004a38c5c8b6294713437 100644 --- a/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue +++ b/src/components/workflows/modals/UpdateWorkflowCredentialsModal.vue @@ -74,7 +74,7 @@ function updateCredentials() { ); formState.loading = true; repo - .checkFileExist("main.nf") + .checkRepoAvailability() .then((result: boolean) => { if (result) { workflowRepository diff --git a/src/components/workflows/modals/UpdateWorkflowModal.vue b/src/components/workflows/modals/UpdateWorkflowModal.vue index 734785589d47f74c7bf23033e801691b9718a5bc..7d064315f284a3cac5098e115f04c0893a8a7c44 100644 --- a/src/components/workflows/modals/UpdateWorkflowModal.vue +++ b/src/components/workflows/modals/UpdateWorkflowModal.vue @@ -13,17 +13,14 @@ import { import BootstrapModal from "@/components/modals/BootstrapModal.vue"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { WorkflowCredentialsService } from "@/client"; -import { - GitRepository, - requiredRepositoryFiles, - determineGitIcon, -} from "@/utils/GitRepository"; +import { GitRepository, determineGitIcon } from "@/utils/GitRepository"; import { valid, lte, inc } from "semver"; import { latestVersion as calculateLatestVersion } from "@/utils/Workflow"; import WorkflowModeTransitionGroup from "@/components/transitions/WorkflowModeTransitionGroup.vue"; import { useWorkflowStore } from "@/stores/workflows"; import BootstrapToast from "@/components/BootstrapToast.vue"; import dayjs from "dayjs"; +import { AxiosError } from "axios"; const workflowRepository = useWorkflowStore(); // Bootstrap Elements @@ -81,7 +78,7 @@ const formState = reactive<{ loadCredentials: boolean; workflowToken?: string; modesEnabled: boolean; - ratelimit_reset: number; + ratelimitReset: number; }>({ loading: false, checkRepoLoading: false, @@ -91,7 +88,7 @@ const formState = reactive<{ loadCredentials: false, workflowToken: undefined, modesEnabled: false, - ratelimit_reset: 0, + ratelimitReset: 0, }); watch( @@ -192,23 +189,28 @@ function checkRepository() { .filter((mode) => mode != undefined) ?? []; // filter all mode objects that are undefined const newModes = formState.modesEnabled ? workflowModes.addModes : []; repo - .checkFilesExist( - requiredRepositoryFiles([...oldModes, ...newModes]), - true, - ) - .then(() => { - formState.allowUpload = true; - }) - .catch((e: Error) => { - try { - const headers = JSON.parse(e.message); - formState.ratelimit_reset = parseInt(headers["x-ratelimit-reset"]); - } catch { - formState.missingFiles = e.message.split(","); + .validateRepo([...oldModes, ...newModes]) + .then((missingFiles) => { + if (missingFiles.length === 0) { + formState.allowUpload = true; + } else { + formState.missingFiles = missingFiles; workflowGitCommitHashElement.value?.setCustomValidity( "Files are missing in the repository", ); } + }) + .catch((e) => { + if ( + e instanceof AxiosError && + e.response != undefined && + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + formState.ratelimitReset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } + console.error(e); }); } } @@ -386,7 +388,7 @@ onMounted(() => { @change="formState.allowUpload = false" /> </div> - <div v-if="formState.ratelimit_reset > 0" class="text-danger"> + <div v-if="formState.ratelimitReset > 0" class="text-danger"> Can't check GitHub repository because the default <a href="https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api?apiVersion=2022-11-28#primary-rate-limit-for-unauthenticated-users" @@ -395,7 +397,7 @@ onMounted(() => { > for your IP address was exhausted <br /> Rate-limit resets - {{ dayjs.unix(formState.ratelimit_reset).fromNow() }} + {{ dayjs.unix(formState.ratelimitReset).fromNow() }} </div> <div v-else-if="formState.missingFiles.length > 0" diff --git a/src/stores/workflows.ts b/src/stores/workflows.ts index 0ec7842b69f0406cceb9435491ddf4b0a02da62e..c58cdf2d1b3781798d1e4fe51a02a9f3a49e1402 100644 --- a/src/stores/workflows.ts +++ b/src/stores/workflows.ts @@ -152,18 +152,18 @@ export const useWorkflowStore = defineStore({ fetchWorkflowDocumentation( workflow_id: string, workflow_version_id: string, - document = DocumentationEnum.USAGE, + document = DocumentationEnum.USAGE_MD, mode_id?: string, // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Promise<any> { if (this.documentationFiles[workflow_version_id] == undefined) { this.documentationFiles[workflow_version_id] = { - changelog: undefined, - clowm_info: undefined, - input: undefined, - output: undefined, - parameter_schema: undefined, - usage: undefined, + "changelog.md": undefined, + "clowm_info.json": undefined, + "input.md": undefined, + "output.md": undefined, + "parameter_schema.json": undefined, + "usage.md": undefined, }; } if ( @@ -185,9 +185,9 @@ export const useWorkflowStore = defineStore({ return response; }) .catch((err) => { - if (document === DocumentationEnum.CLOWM_INFO) { + if (document === DocumentationEnum.CLOWM_INFO_JSON) { this.documentationFiles[workflow_version_id][ - DocumentationEnum.CLOWM_INFO + DocumentationEnum.CLOWM_INFO_JSON ] = {}; } return err; diff --git a/src/utils/GitRepository.ts b/src/utils/GitRepository.ts index 1ab756513b1f92f126b345113d91d044cc27506f..f31181d1b822ef02754d33754cb48899c4cb2bd4 100644 --- a/src/utils/GitRepository.ts +++ b/src/utils/GitRepository.ts @@ -1,17 +1,11 @@ -import axios, { AxiosError } from "axios"; +import axios from "axios"; import type { AxiosInstance, AxiosBasicCredentials } from "axios"; import type { WorkflowModeOut, WorkflowModeIn } from "@/client"; -export function requiredRepositoryFiles( +export function requiredDocumentationFiles( modes?: WorkflowModeIn[] | WorkflowModeOut[], ): string[] { - const list = [ - "main.nf", - "CHANGELOG.md", - "README.md", - "docs/usage.md", - "docs/output.md", - ]; + const list = ["CHANGELOG.md", "README.md", "docs/usage.md", "docs/output.md"]; if (modes && modes.length > 0) { list.push(...modes.map((mode) => mode.schema_path)); } else { @@ -34,11 +28,15 @@ export function determineGitIcon(repo_url?: string): string { return "fa-brands fa-".concat(gitProvider); } +const capturingMainScriptRegex = + /manifest\s+{[\s\S]+mainScript\s+=\s+["'](?<scriptname>\S+)["']/; + export abstract class GitRepository { protected repo: URL; protected gitCommitHash: string; protected token?: string; protected httpClient: AxiosInstance; + protected abstract readonly repoAvailabilityUrl: string; protected constructor( repoUrl: string, @@ -46,7 +44,7 @@ export abstract class GitRepository { token?: string, ) { this.repo = new URL(repoUrl); - this.gitCommitHash = gitCommitHash; + this.gitCommitHash = encodeURIComponent(gitCommitHash); if (token != undefined && token.length > 0) { this.token = token; } @@ -55,78 +53,82 @@ export abstract class GitRepository { }); } - async checkFileExist(filepath: string): Promise<boolean> { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + async downloadFile(filepath: string): Promise<any> { + return await this.httpClient.get(await this.downloadFileUrl(filepath)); + } + + async checkRepoAvailability(): Promise<boolean> { try { - await this.httpClient.get(this.fileUrl(filepath)); + await this.httpClient.get(this.repoAvailabilityUrl); + return true; } catch (e) { - if ( - e instanceof AxiosError && - e.response != undefined && - parseInt(e.response?.headers["x-ratelimit-remaining"] ?? "0") == 0 - ) { - const filtered: Record<string, string> = Object.keys(e.response.headers) - .filter((key) => key.startsWith("x-")) - .reduce( - (obj, key) => { - obj[key] = e.response?.headers[key] ?? ""; - return obj; - }, - {} as Record<string, string>, - ); - throw new Error(JSON.stringify(filtered)); - } return false; } - return true; } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - async downloadFile(filepath: string): Promise<any> { - try { - return await this.httpClient.get(await this.downloadFileUrl(filepath)); - } catch (e) { - if ( - e instanceof AxiosError && - e.response != undefined && - parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 - ) { - //console.log(parseInt(e.response.headers["x-ratelimit-reset"] ?? "0")); - const filtered: Record<string, string> = Object.keys(e.response.headers) - .filter((key) => key.startsWith("x-")) - .reduce( - (obj, key) => { - obj[key] = e.response?.headers[key] ?? ""; - return obj; - }, - {} as Record<string, string>, - ); - throw new Error(JSON.stringify(filtered)); + async validateRepo( + modes: WorkflowModeIn[] | WorkflowModeOut[], + ): Promise<string[]> { + const files = await this.getFileList(); + const missingFiles: string[] = []; + const filteredFiles = files.filter( + // files in base directory and docs directory + (file) => !file.includes("/") || file.startsWith("docs/"), + ); + // Check if main.nf is there and if not, check for an alternative name in the nextflow.config + if (filteredFiles.findIndex((file) => file === "main.nf") === -1) { + try { + const nextflowConfig = (await this.downloadFile("nextflow.config")) + .data; + const entryScriptName = nextflowConfig.match(capturingMainScriptRegex) + .groups?.["scriptname"]; + if (entryScriptName == undefined) { + missingFiles.push("main.nf"); + } else if (files.findIndex((file) => file === entryScriptName) === -1) { + missingFiles.push(entryScriptName); + } + } catch (e) { + console.error(e); + missingFiles.push("main.nf"); } - return ""; } - } - async checkFilesExist( - files: string[], - raiseError = false, - ): Promise<boolean[]> { - const checks = Promise.all( - files.map((filepath) => this.checkFileExist(filepath)), - ); - const results = await checks; - if (raiseError) { - const missingFiles = files.filter((file, index) => !results[index]); - if (missingFiles.length > 0) { - throw new Error(missingFiles.reduce((a, b) => a + "," + b)); + // check mandatory files + const requiredFileList = [ + "CHANGELOG.md", + "README.md", + "docs/usage.md", + "docs/output.md", + ]; + for (const requiredFile of requiredFileList) { + if (filteredFiles.findIndex((file) => file === requiredFile) === -1) { + missingFiles.push(requiredFile); + } + } + // Check required files for modes + if (modes && modes.length > 0) { + for (const requiredFile of modes.map((mode) => mode.schema_path)) { + if (files.findIndex((file) => file === requiredFile) === -1) { + missingFiles.push(requiredFile); + } + } + } else { + // if there are no modes, check standard path for parameter schema + if ( + filteredFiles.findIndex((file) => file === "nextflow_schema.json") === + -1 + ) { + missingFiles.push("nextflow_schema.json"); } } - return results; + return missingFiles; } - protected abstract fileUrl(filepath: string): string; - protected abstract downloadFileUrl(filepath: string): Promise<string>; + abstract getFileList(): Promise<string[]>; + static buildRepository( repoUrl: string, gitCommitHash: string, @@ -144,12 +146,14 @@ export abstract class GitRepository { class GithubRepository extends GitRepository { private readonly account: string; private readonly repoName: string; + protected readonly repoAvailabilityUrl: string; constructor(repoUrl: string, gitCommitHash: string, token?: string) { super(repoUrl, gitCommitHash, token); const pathParts = this.repo.pathname.slice(1).split("/"); this.account = pathParts[0]; this.repoName = pathParts[1]; + this.repoAvailabilityUrl = `https://api.github.com/repos/${this.account}/${this.repoName}/git/commits/${this.gitCommitHash}`; if (token) { this.httpClient.interceptors.request.use((req) => { if (!req.url?.includes("raw")) { @@ -173,9 +177,7 @@ class GithubRepository extends GitRepository { fileUrl(filepath: string): string { return `https://api.github.com/repos/${this.account}/${ this.repoName - }/contents/${encodeURIComponent(filepath)}?ref=${encodeURIComponent( - this.gitCommitHash, - )}`; + }/contents/${encodeURIComponent(filepath)}?ref=${this.gitCommitHash}`; } protected async downloadFileUrl(filepath: string): Promise<string> { @@ -190,17 +192,30 @@ class GithubRepository extends GitRepository { ) ).data["download_url"]; } + + async getFileList(): Promise<string[]> { + const response = await this.httpClient.get( + `https://api.github.com/repos/${this.account}/${ + this.repoName + }/git/trees/${this.gitCommitHash}?recursive=true`, + ); + return response.data["tree"].map( + (file: Record<string, string>): string => file["path"], + ); + } } class GitlabRepository extends GitRepository { private readonly host: string; private readonly project: string; + protected readonly repoAvailabilityUrl: string; constructor(repoUrl: string, gitCommitHash: string, token?: string) { super(repoUrl, gitCommitHash, token); const url = new URL(repoUrl); this.host = url.host; - this.project = url.pathname.slice(1); + this.project = encodeURIComponent(url.pathname.slice(1)); + this.repoAvailabilityUrl = `https://${this.host}/api/v4/projects/${this.project}/repository/commits/${this.gitCommitHash}?stats=false`; if (token != undefined) { this.httpClient.interceptors.request.use((req) => { req.headers.setAuthorization(`Bearer ${token}`); @@ -209,21 +224,45 @@ class GitlabRepository extends GitRepository { } } - fileUrl(filepath: string): string { - return `https://${this.host}/api/v4/projects/${encodeURIComponent( - this.project, - )}/repository/files/${encodeURIComponent( - filepath, - )}?ref=${encodeURIComponent(this.gitCommitHash)}`; - } - protected downloadFileUrl(filepath: string): Promise<string> { return Promise.resolve( - `https://${this.host}/api/v4/projects/${encodeURIComponent( - this.project, - )}/repository/files/${encodeURIComponent( + `https://${this.host}/api/v4/projects/${ + this.project + }/repository/files/${encodeURIComponent( filepath, - )}/raw?ref=${encodeURIComponent(this.gitCommitHash)}`, + )}/raw?ref=${this.gitCommitHash}`, ); } + + async getFileList(): Promise<string[]> { + const fileList: string[] = []; + try { + let response = await this.httpClient.get( + `https://${this.host}/api/v4/projects/${ + this.project + }/repository/tree?ref=${this.gitCommitHash}&recursive=true&per_page=100&pagination=keyset&order_by=id&sort=asc`, + ); + fileList.push( + ...response.data.map( + (file: Record<string, string>): string => file["path"], + ), + ); + while (response.headers?.["link"] != undefined) { + const linkHeader: string = response.headers?.["link"]; + + response = await this.httpClient.get( + linkHeader.split(";")[0].slice(1, -1), + ); + fileList.push( + ...response.data.map( + (file: Record<string, string>): string => file["path"], + ), + ); + } + return fileList; + } catch (e) { + console.error(e); + throw new Error("repo not reachable"); + } + } } diff --git a/src/views/workflows/ArbitraryWorkflowView.vue b/src/views/workflows/ArbitraryWorkflowView.vue index b2c6aa0ada130d957c07920c8a82277686a32f4b..0828836465e88fee9c16629cc66f5859b79bda4d 100644 --- a/src/views/workflows/ArbitraryWorkflowView.vue +++ b/src/views/workflows/ArbitraryWorkflowView.vue @@ -1,7 +1,10 @@ <script setup lang="ts"> import WorkflowDocumentationTabs from "@/components/workflows/WorkflowDocumentationTabs.vue"; import { onMounted, reactive, ref, watch } from "vue"; -import { GitRepository, requiredRepositoryFiles } from "@/utils/GitRepository"; +import { + GitRepository, + requiredDocumentationFiles, +} from "@/utils/GitRepository"; import FontAwesomeIcon from "@/components/FontAwesomeIcon.vue"; import { useRouter } from "vue-router"; @@ -16,6 +19,7 @@ import type { FlatWorkflowParameters, } from "@/types/WorkflowParameters"; import dayjs from "dayjs"; +import { AxiosError } from "axios"; const props = defineProps<{ wid: string; @@ -61,7 +65,7 @@ function downloadVersionFiles() { if (workflowState.workflow) { workflowState.loading = true; Promise.all( - requiredRepositoryFiles(workflowState.workflow.modes).map((file) => + requiredDocumentationFiles(workflowState.workflow.modes).map((file) => workflowState.repo.downloadFile(file).then((response) => { if (file.includes("README")) { workflowState.descriptionMarkdown = response.data; @@ -78,10 +82,15 @@ function downloadVersionFiles() { ), ) .catch((e) => { - const headers = JSON.parse(e.message) as Record<string, string>; - workflowState.ratelimit_reset = parseInt( - headers["x-ratelimit-reset"] ?? "0", - ); + if ( + e instanceof AxiosError && + e.response != undefined && + parseInt(e.response.headers["x-ratelimit-remaining"] ?? "1") == 0 + ) { + workflowState.ratelimit_reset = parseInt( + e.response.headers["x-ratelimit-reset"] ?? "0", + ); + } }) .finally(() => { workflowState.loading = false; diff --git a/src/views/workflows/CreateClowmInfoView.vue b/src/views/workflows/CreateClowmInfoView.vue index ec50dc22d3376952689d8fa474dc86cc414e983f..5398b353cca2d746fddc18da491bdbeba81083ed 100644 --- a/src/views/workflows/CreateClowmInfoView.vue +++ b/src/views/workflows/CreateClowmInfoView.vue @@ -39,8 +39,9 @@ const parameterPools = reactive<{ // eslint-disable-next-line @typescript-eslint/no-explicit-any const parameterSchema = computed<Record<string, Record<string, any>>>(() => { const schema = - workflowRepository.documentationFiles[props.workflow_version_id ?? ""] - ?.parameter_schema; + workflowRepository.documentationFiles[props.workflow_version_id ?? ""]?.[ + "parameter_schema.json" + ]; const a = schema?.["properties"] ?? {}; for (const group in schema?.["definitions"] ?? {}) { for (const param in schema?.["definitions"]?.[group]?.["properties"] ?? @@ -65,7 +66,7 @@ const downloadUrl = computed<string>(() => watch( () => workflowRepository.documentationFiles[props.workflow_version_id ?? ""], (newVal, old) => { - if (newVal != old && newVal?.parameter_schema != undefined) { + if (newVal != old && newVal?.["parameter_schema.json"] != undefined) { updateParameterPools(newVal); } }, @@ -76,8 +77,8 @@ watch( // eslint-disable-next-line @typescript-eslint/no-explicit-any function updateParameterPools(newVal?: Record<DocumentationEnum, any>) { - if (newVal?.parameter_schema) { - const parameters = extractParameterList(newVal.parameter_schema); + if (newVal?.["parameter_schema.json"]) { + const parameters = extractParameterList(newVal["parameter_schema.json"]); parameterPools.input = parameters.slice(); parameterPools.output = parameters.slice(); parameterPools.resources = parameters.slice(); @@ -87,8 +88,14 @@ function updateParameterPools(newVal?: Record<DocumentationEnum, any>) { infoState.exampleParameters = undefined; infoState.resourceParameters = undefined; } - if (newVal?.clowm_info && Object.keys(newVal.clowm_info).length > 0) { - Object.assign(infoState, JSON.parse(JSON.stringify(newVal.clowm_info))); + if ( + newVal?.["clowm_info.json"] && + Object.keys(newVal["clowm_info.json"]).length > 0 + ) { + Object.assign( + infoState, + JSON.parse(JSON.stringify(newVal["clowm_info.json"])), + ); parameterPools.input = parameterPools.input.filter( (param) => !infoState.inputParameters.includes(param), ); @@ -177,12 +184,12 @@ onMounted(() => { workflowRepository.fetchWorkflowDocumentation( props.workflow_id, props.workflow_version_id, - DocumentationEnum.PARAMETER_SCHEMA, + DocumentationEnum.PARAMETER_SCHEMA_JSON, ), workflowRepository.fetchWorkflowDocumentation( props.workflow_id, props.workflow_version_id, - DocumentationEnum.CLOWM_INFO, + DocumentationEnum.CLOWM_INFO_JSON, ), ]).finally(() => { updateParameterPools( diff --git a/src/views/workflows/CreateParameterTranslationView.vue b/src/views/workflows/CreateParameterTranslationView.vue index 7f9a104cef8e3e2fad950700732b128b73d5e66f..8e67c0608b909c6089adcd3fe7742856a265406f 100644 --- a/src/views/workflows/CreateParameterTranslationView.vue +++ b/src/views/workflows/CreateParameterTranslationView.vue @@ -59,7 +59,9 @@ const parameterPools = reactive<{ // ============================================================================= watch( () => - workflowRepository.documentationFiles[props.versionId]?.parameter_schema, + workflowRepository.documentationFiles[props.versionId]?.[ + "parameter_schema.json" + ], (newVal, old) => { if (newVal != old && newVal) { updateParameterPools(newVal); @@ -68,7 +70,8 @@ watch( ); watch( - () => workflowRepository.documentationFiles[props.versionId]?.clowm_info, + () => + workflowRepository.documentationFiles[props.versionId]?.["clowm_info.json"], (newVal, old) => { if (newVal != old && newVal) { updateResourceParameters(newVal); @@ -81,8 +84,9 @@ watch( // eslint-disable-next-line @typescript-eslint/no-explicit-any const parameterSchema = computed<Record<string, Record<string, any>>>(() => { const schema = - workflowRepository.documentationFiles[props.versionId ?? ""] - ?.parameter_schema; + workflowRepository.documentationFiles[props.versionId ?? ""]?.[ + "parameter_schema.json" + ]; const a = schema?.["properties"] ?? {}; for (const group in schema?.["definitions"] ?? {}) { for (const param in schema?.["definitions"]?.[group]?.["properties"] ?? @@ -231,9 +235,9 @@ function makeResourceParameterMapping(param: string, val: string) { function deleteDefaultParameter(param: string) { if ( - !workflowRepository.documentationFiles[ - props.versionId - ]?.clowm_info?.resourceParameters?.includes(param) + !workflowRepository.documentationFiles[props.versionId]?.[ + "clowm_info.json" + ]?.resourceParameters?.includes(param) ) { parameterState.resourceParametersDefault.delete(param); } @@ -276,9 +280,9 @@ function deleteMappingParameterValue(param: string, val: string) { function deleteMappingParameter(param: string) { if ( - !workflowRepository.documentationFiles[ - props.versionId - ]?.clowm_info?.resourceParameters?.includes(param) + !workflowRepository.documentationFiles[props.versionId]?.[ + "clowm_info.json" + ]?.resourceParameters?.includes(param) ) { parameterState.resourceParametersMapping.delete(param); } @@ -297,11 +301,14 @@ function deleteParameterExtension() { .then(() => { parameterState.extension = {}; updateParameterPools( - workflowRepository.documentationFiles[props.versionId] - ?.parameter_schema, + workflowRepository.documentationFiles[props.versionId]?.[ + "parameter_schema.json" + ], ); updateResourceParameters( - workflowRepository.documentationFiles[props.versionId]?.clowm_info, + workflowRepository.documentationFiles[props.versionId]?.[ + "clowm_info.json" + ], ); deleteToast?.show(); }) @@ -335,25 +342,28 @@ onMounted(() => { .fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, + DocumentationEnum.PARAMETER_SCHEMA_JSON, workflowRepository.ownVersionMapping[props.versionId]?.modes?.[0], ) .then(() => workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.CLOWM_INFO, + DocumentationEnum.CLOWM_INFO_JSON, workflowRepository.ownVersionMapping[props.versionId]?.modes?.[0], ), ) .finally(() => { parameterState.loading = false; updateParameterPools( - workflowRepository.documentationFiles[props.versionId] - ?.parameter_schema, + workflowRepository.documentationFiles[props.versionId]?.[ + "parameter_schema.json" + ], ); updateResourceParameters( - workflowRepository.documentationFiles[props.versionId]?.clowm_info, + workflowRepository.documentationFiles[props.versionId]?.[ + "clowm_info.json" + ], ); }); }); diff --git a/src/views/workflows/StartWorkflowView.vue b/src/views/workflows/StartWorkflowView.vue index d736a62ab2dd26e3b82587a97ec09b66f10212c9..ee228ff981d9d0db6833b2f427bd8f5e73f31f86 100644 --- a/src/views/workflows/StartWorkflowView.vue +++ b/src/views/workflows/StartWorkflowView.vue @@ -53,7 +53,7 @@ function downloadParameterSchema() { .fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, + DocumentationEnum.PARAMETER_SCHEMA_JSON, props.workflowModeId, ) .then((schema) => { @@ -62,7 +62,7 @@ function downloadParameterSchema() { workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.CLOWM_INFO, + DocumentationEnum.CLOWM_INFO_JSON, ), ]).finally(() => { versionState.loading = false; @@ -131,7 +131,7 @@ onMounted(() => { " :clowm-info=" workflowRepository.documentationFiles[versionId]?.[ - DocumentationEnum.CLOWM_INFO + DocumentationEnum.CLOWM_INFO_JSON ] " :parameter-extension=" diff --git a/src/views/workflows/WorkflowVersionView.vue b/src/views/workflows/WorkflowVersionView.vue index 4d01da538dcdbfa5bcb22b81e1430ea14f7f65cd..a4a7deb6661e2cee9ab5e4cadc75c62e187b51de 100644 --- a/src/views/workflows/WorkflowVersionView.vue +++ b/src/views/workflows/WorkflowVersionView.vue @@ -40,7 +40,7 @@ watch( .fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, + DocumentationEnum.PARAMETER_SCHEMA_JSON, newModeId, ) .then((schema) => { @@ -55,18 +55,18 @@ function downloadVersionFiles() { const descriptionPromise = workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.USAGE, + DocumentationEnum.USAGE_MD, ); const changelogPromise = workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.CHANGELOG, + DocumentationEnum.CHANGELOG_MD, ); const parameterPromise = workflowRepository .fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.PARAMETER_SCHEMA, + DocumentationEnum.PARAMETER_SCHEMA_JSON, props.workflowModeId, ) .then((schema) => { @@ -75,12 +75,12 @@ function downloadVersionFiles() { const usagePromise = workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.INPUT, + DocumentationEnum.INPUT_MD, ); const outputPromise = workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.OUTPUT, + DocumentationEnum.OUTPUT_MD, ); Promise.all([ descriptionPromise, @@ -91,7 +91,7 @@ function downloadVersionFiles() { workflowRepository.fetchWorkflowDocumentation( props.workflowId, props.versionId, - DocumentationEnum.CLOWM_INFO, + DocumentationEnum.CLOWM_INFO_JSON, ), ]).finally(() => { versionState.fileLoading = false; @@ -108,28 +108,28 @@ onMounted(() => { :parameter-schema="versionState.parameterSchema" :clowm-info=" workflowRepository.documentationFiles[props.versionId]?.[ - DocumentationEnum.CLOWM_INFO + DocumentationEnum.CLOWM_INFO_JSON ] " :loading="versionState.fileLoading" :changelog-markdown=" workflowRepository.documentationFiles[props.versionId]?.[ - DocumentationEnum.CHANGELOG + DocumentationEnum.CHANGELOG_MD ] " :description-markdown=" workflowRepository.documentationFiles[props.versionId]?.[ - DocumentationEnum.USAGE + DocumentationEnum.USAGE_MD ] " :usage-markdown=" workflowRepository.documentationFiles[props.versionId]?.[ - DocumentationEnum.INPUT + DocumentationEnum.INPUT_MD ] " :output-markdown=" workflowRepository.documentationFiles[props.versionId]?.[ - DocumentationEnum.OUTPUT + DocumentationEnum.OUTPUT_MD ] " />