diff --git a/.gitignore b/.gitignore index 5d889f542ad1b6de7093a00bd316a0e421d049f5..7a4b14b17f6c684d9bb8d343f5523e66c3157453 100644 --- a/.gitignore +++ b/.gitignore @@ -350,6 +350,6 @@ Sessionx.vim .netrwhist *~ # Auto-generated tag files -tags +# tags # Persistent undo [._]*.un~ diff --git a/.gitmodules b/.gitmodules index f612bc05fef311547b546e68da614a7aec463793..139e244614dce96d58e362ede9d99239d56c2271 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = ../../shared/jetbrains_configuration.git [submodule "NodeApp/src/shared"] path = NodeApp/src/shared - url = ../../shared/nodesharedcode.git + url = https://gitedu.hesge.ch/dojo_project/projects/shared/nodesharedcode [submodule "NodeApp/src/sharedByClients"] path = NodeApp/src/sharedByClients - url = ../../shared/nodeclientsharedcode.git + url = https://gitedu.hesge.ch/dojo_project/projects/shared/nodeclientsharedcode \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..b58b603fea78041071d125a30db58d79b3d49217 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/dojocli.iml b/.idea/dojocli.iml new file mode 100644 index 0000000000000000000000000000000000000000..24643cc37449b4bde54411a80b8ed61258225e34 --- /dev/null +++ b/.idea/dojocli.iml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="WEB_MODULE" version="4"> + <component name="NewModuleRootManager"> + <content url="file://$MODULE_DIR$"> + <excludeFolder url="file://$MODULE_DIR$/.tmp" /> + <excludeFolder url="file://$MODULE_DIR$/temp" /> + <excludeFolder url="file://$MODULE_DIR$/tmp" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + </component> +</module> \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..4ab30517f783babc3ee1b319137f7aef281d02af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/.idea/dojocli.iml" filepath="$PROJECT_DIR$/.idea/dojocli.iml" /> + </modules> + </component> +</project> \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="" vcs="Git" /> + </component> +</project> \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d2d28342a81587602e6f84a7bab5f2c4ef00680..ff0f2b11db8d270ede26780bb75dcf919040ea13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,11 @@ - No modifications / Keep major and minors versions in sync with all parts of the project --> +## 4.2.0 (Upcoming) + +### ✨ Feature +- Add support for tags on assignments and exercises + ## 4.1.1 (2024-05-28) @@ -27,7 +32,7 @@ ## 4.1.0 (2024-05-28) ### ✨ Feature -- Add features related to corrige (commentary, commit specific link / update, delete link) +- Add features related to corrige (commentary, commit a specific link / update, delete link) ### 🎨 Interface - Ask for confirmation before creating an exercise that already exists diff --git a/NodeApp/.gitlab-ci/01_functions.yml b/NodeApp/.gitlab-ci/01_functions.yml index 83e624592f6847ffaca7aa0d5bc06a71493d35f5..f3ca0ffab9b95592774020d65bfa43e2aa69304b 100644 --- a/NodeApp/.gitlab-ci/01_functions.yml +++ b/NodeApp/.gitlab-ci/01_functions.yml @@ -92,7 +92,7 @@ sed -i -r "s/,[\ \n]*\}/\}/g" src/init.ts echo "DOTENV_KEY_PRODUCTION=\"${DOTENV_PROD_KEY}\"" > .env.keys - npx @dotenvx/dotenvx decrypt + npx @dotenvx/dotenvx@0.45.0 decrypt mv .env.production .env rm .env.keys fi diff --git a/NodeApp/.idea/material_theme_project_new.xml b/NodeApp/.idea/material_theme_project_new.xml new file mode 100644 index 0000000000000000000000000000000000000000..16e830f2d917c0815e814dcbf57e219c3b85c2e2 --- /dev/null +++ b/NodeApp/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="MaterialThemeProjectNewConfig"> + <option name="metadata"> + <MTProjectMetadataState> + <option name="migrated" value="true" /> + <option name="pristineConfig" value="false" /> + <option name="userId" value="104e8585:19002424fea:-7ffe" /> + </MTProjectMetadataState> + </option> + </component> +</project> \ No newline at end of file diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json index 6bb6f37914629570747b79e26795d2aa055cc242..e52fbce9fe43a25f2bc90f42b46f5d6ada4cbb44 100644 --- a/NodeApp/package-lock.json +++ b/NodeApp/package-lock.json @@ -1,16 +1,16 @@ { "name": "dojo_cli", - "version": "4.1.1", + "version": "4.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dojo_cli", - "version": "4.1.1", + "version": "4.2.0", "license": "AGPLv3", "dependencies": { - "@dotenvx/dotenvx": "^0.44.1", - "@eslint/js": "^9.3.0", + "@dotenvx/dotenvx": "^0.45.0", + "@eslint/js": "^9.6.0", "@gitbeaker/core": "^40.0.3", "@gitbeaker/requester-utils": "^40.0.3", "@gitbeaker/rest": "^40.0.3", @@ -18,6 +18,7 @@ "axios": "^1.7.2", "boxen": "^5.1.2", "chalk": "^4.1.2", + "cli-table3": "^0.6.5", "commander": "^12.1.0", "form-data": "^4.0.0", "fs-extra": "^11.2.0", @@ -31,7 +32,7 @@ "tar-stream": "^3.1.7", "winston": "^3.13.0", "winston-transport": "^4.7.0", - "yaml": "^2.4.2", + "yaml": "^2.4.5", "zod": "^3.23.8", "zod-validation-error": "^3.3.0" }, @@ -42,19 +43,20 @@ "@types/fs-extra": "^11.0.4", "@types/inquirer": "^8.2.10", "@types/jsonwebtoken": "^8.5.9", - "@types/node": "^18.19.33", + "@types/node": "^18.19.39", "@types/semver": "^7.5.8", "@types/tar-stream": "^3.1.3", - "@typescript-eslint/eslint-plugin": "^7.11.0", - "@typescript-eslint/parser": "^7.11.0", - "dotenv-vault": "^1.26.1", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser": "^7.15.0", + "dotenv-cli": "^7.4.2", + "dotenv-vault": "^1.26.2", "eslint": "^8.57.0", "genversion": "^3.2.0", "pkg": "^5.8.1", "tiny-typed-emitter": "^2.1.0", - "tsx": "^4.11.0", - "typescript": "^5.4.5", - "typescript-eslint": "^7.11.0" + "tsx": "^4.16.2", + "typescript": "^5.5.3", + "typescript-eslint": "^7.15.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -165,9 +167,9 @@ } }, "node_modules/@dotenvx/dotenvx": { - "version": "0.44.1", - "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-0.44.1.tgz", - "integrity": "sha512-OmOU7CRwhXydZUHeTP46GNZsGpwQ3mwrr3cUAWod+FmrKW3ib4GYe1jU++ZFyEEUNvg532QvvM7hQ44YyJrgfw==", + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-0.45.0.tgz", + "integrity": "sha512-qjW6PDX3mONGQHSrI6LPzOCHn/RZPZuHThPrRZOvyo90pTMGQaCqRbcastl1Y3ZQlw0igdcKnk9pJSh5uYNLbg==", "dependencies": { "@inquirer/confirm": "^2.0.17", "arch": "^2.1.1", @@ -216,9 +218,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", - "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], @@ -232,9 +234,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", - "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], @@ -248,9 +250,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", - "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], @@ -264,9 +266,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", - "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], @@ -280,9 +282,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", - "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], @@ -296,9 +298,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", - "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], @@ -312,9 +314,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", - "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], @@ -328,9 +330,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", - "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], @@ -344,9 +346,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", - "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], @@ -360,9 +362,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", - "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], @@ -376,9 +378,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", - "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], @@ -392,9 +394,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", - "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], @@ -408,9 +410,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", - "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], @@ -424,9 +426,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", - "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], @@ -440,9 +442,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", - "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], @@ -456,9 +458,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", - "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], @@ -472,9 +474,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", - "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], @@ -488,9 +490,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", - "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], @@ -504,9 +506,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", - "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], @@ -520,9 +522,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", - "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], @@ -536,9 +538,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", - "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], @@ -552,9 +554,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", - "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", "cpu": [ "ia32" ], @@ -568,9 +570,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", - "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], @@ -693,9 +695,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.3.0.tgz", - "integrity": "sha512-niBqk8iwv96+yuTwjM6bWg8ovzAPF9qkICsGtcoa5/dmqcEMfdwNAX7+/OHcJHc7wj7XqPxH98oAHytFYlw6Sw==", + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", + "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -1613,9 +1615,9 @@ } }, "node_modules/@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "version": "18.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.39.tgz", + "integrity": "sha512-nPwTRDKUctxw3di5b4TfT3I0sWDiWoPQCZjXhvdkINntwr8lcoVCKsTgnXeRubKIlfnV+eN/HYk6Jb40tbcEAQ==", "dependencies": { "undici-types": "~5.26.4" } @@ -1655,16 +1657,16 @@ "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz", - "integrity": "sha512-P+qEahbgeHW4JQ/87FuItjBj8O3MYv5gELDzr8QaQ7fsll1gSMTYb6j87MYyxwf3DtD7uGFB9ShwgmCJB5KmaQ==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.15.0.tgz", + "integrity": "sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/type-utils": "7.11.0", - "@typescript-eslint/utils": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/type-utils": "7.15.0", + "@typescript-eslint/utils": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -1688,15 +1690,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz", - "integrity": "sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.15.0.tgz", + "integrity": "sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", "debug": "^4.3.4" }, "engines": { @@ -1716,13 +1718,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.11.0.tgz", - "integrity": "sha512-27tGdVEiutD4POirLZX4YzT180vevUURJl4wJGmm6TrQoiYwuxTIY98PBp6L2oN+JQxzE0URvYlzJaBHIekXAw==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.15.0.tgz", + "integrity": "sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0" + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1733,13 +1735,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.11.0.tgz", - "integrity": "sha512-WmppUEgYy+y1NTseNMJ6mCFxt03/7jTOy08bcg7bxJJdsM4nuhnchyBbE8vryveaJUf62noH7LodPSo5Z0WUCg==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.15.0.tgz", + "integrity": "sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "7.11.0", - "@typescript-eslint/utils": "7.11.0", + "@typescript-eslint/typescript-estree": "7.15.0", + "@typescript-eslint/utils": "7.15.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -1760,9 +1762,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.11.0.tgz", - "integrity": "sha512-MPEsDRZTyCiXkD4vd3zywDCifi7tatc4K37KqTprCvaXptP7Xlpdw0NR2hRJTetG5TxbWDB79Ys4kLmHliEo/w==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", "dev": true, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1773,13 +1775,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.11.0.tgz", - "integrity": "sha512-cxkhZ2C/iyi3/6U9EPc5y+a6csqHItndvN/CzbNXTNrsC3/ASoYQZEt9uMaEp+xFNjasqQyszp5TumAVKKvJeQ==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.15.0.tgz", + "integrity": "sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/visitor-keys": "7.11.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/visitor-keys": "7.15.0", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1801,15 +1803,15 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.11.0.tgz", - "integrity": "sha512-xlAWwPleNRHwF37AhrZurOxA1wyXowW4PqVXZVUNCLjB48CqdPJoJWkrpH2nij9Q3Lb7rtWindtoXwxjxlKKCA==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.15.0.tgz", + "integrity": "sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.11.0", - "@typescript-eslint/types": "7.11.0", - "@typescript-eslint/typescript-estree": "7.11.0" + "@typescript-eslint/scope-manager": "7.15.0", + "@typescript-eslint/types": "7.15.0", + "@typescript-eslint/typescript-estree": "7.15.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -1823,12 +1825,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.11.0.tgz", - "integrity": "sha512-7syYk4MzjxTEk0g/w3iqtgxnFQspDJfn6QKD36xMuuhTzjcxY7F8EmBLnALjVyaOF1/bVocu3bS/2/F7rXrveQ==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.15.0.tgz", + "integrity": "sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "7.11.0", + "@typescript-eslint/types": "7.15.0", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2349,6 +2351,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-table3": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "@colors/colors": "1.5.0" + } + }, + "node_modules/cli-table3/node_modules/@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "optional": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/cli-width": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", @@ -2726,6 +2751,30 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-7.4.2.tgz", + "integrity": "sha512-SbUj8l61zIbzyhIbg0FwPJq6+wjbzdn9oEtozQpZ6kW2ihCcapKVZj49oCT3oPM+mgQm+itgvUQcG5szxVrZTA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv-expand": { "version": "11.0.6", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.6.tgz", @@ -2741,9 +2790,9 @@ } }, "node_modules/dotenv-vault": { - "version": "1.26.1", - "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.26.1.tgz", - "integrity": "sha512-v+RK6LXpJQWhaelTT2s0b5FQB0qziRBuGCrAgAeDHtgkDEA0NqF7OXYXsrnKTuCPnwBg0FmNJr4lZebCpJnrFA==", + "version": "1.26.2", + "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.26.2.tgz", + "integrity": "sha512-nURqmc3kii3kqiZXcBYdt0QrjpXBjXWtzevCwC9FRbIjwKenGoN/bZHC9l9ueYI3gGoKjgt/1Cmno6HvzgMlDA==", "dev": true, "dependencies": { "@oclif/core": "^1", @@ -2859,9 +2908,9 @@ } }, "node_modules/esbuild": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", - "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, "bin": { @@ -2871,29 +2920,29 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.20.2", - "@esbuild/android-arm": "0.20.2", - "@esbuild/android-arm64": "0.20.2", - "@esbuild/android-x64": "0.20.2", - "@esbuild/darwin-arm64": "0.20.2", - "@esbuild/darwin-x64": "0.20.2", - "@esbuild/freebsd-arm64": "0.20.2", - "@esbuild/freebsd-x64": "0.20.2", - "@esbuild/linux-arm": "0.20.2", - "@esbuild/linux-arm64": "0.20.2", - "@esbuild/linux-ia32": "0.20.2", - "@esbuild/linux-loong64": "0.20.2", - "@esbuild/linux-mips64el": "0.20.2", - "@esbuild/linux-ppc64": "0.20.2", - "@esbuild/linux-riscv64": "0.20.2", - "@esbuild/linux-s390x": "0.20.2", - "@esbuild/linux-x64": "0.20.2", - "@esbuild/netbsd-x64": "0.20.2", - "@esbuild/openbsd-x64": "0.20.2", - "@esbuild/sunos-x64": "0.20.2", - "@esbuild/win32-arm64": "0.20.2", - "@esbuild/win32-ia32": "0.20.2", - "@esbuild/win32-x64": "0.20.2" + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" } }, "node_modules/escalade": { @@ -6038,12 +6087,12 @@ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tsx": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.11.0.tgz", - "integrity": "sha512-vzGGELOgAupsNVssAmZjbUDfdm/pWP4R+Kg8TVdsonxbXk0bEpE1qh0yV6/QxUVXaVlNemgcPajGdJJ82n3stg==", + "version": "4.16.2", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz", + "integrity": "sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==", "dev": true, "dependencies": { - "esbuild": "~0.20.2", + "esbuild": "~0.21.5", "get-tsconfig": "^4.7.5" }, "bin": { @@ -6092,9 +6141,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.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz", + "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -6105,14 +6154,14 @@ } }, "node_modules/typescript-eslint": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.11.0.tgz", - "integrity": "sha512-ZKe3yHF/IS/kCUE4CGE3UgtK+Q7yRk1e9kwEI0rqm9XxMTd9P1eHe0LVVtrZ3oFuIQ2unJ9Xn0vTsLApzJ3aPw==", + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-7.15.0.tgz", + "integrity": "sha512-Ta40FhMXBCwHura4X4fncaCVkVcnJ9jnOq5+Lp4lN8F4DzHZtOwZdRvVBiNUGznUDHPwdGnrnwxmUOU2fFQqFA==", "dev": true, "dependencies": { - "@typescript-eslint/eslint-plugin": "7.11.0", - "@typescript-eslint/parser": "7.11.0", - "@typescript-eslint/utils": "7.11.0" + "@typescript-eslint/eslint-plugin": "7.15.0", + "@typescript-eslint/parser": "7.15.0", + "@typescript-eslint/utils": "7.15.0" }, "engines": { "node": "^18.18.0 || >=20.0.0" @@ -6321,9 +6370,9 @@ } }, "node_modules/yaml": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.2.tgz", - "integrity": "sha512-B3VqDZ+JAg1nZpaEmWtTXUlBneoGx6CPM9b0TENK6aoSu5t73dItudwdgmi6tHlIZZId4dZ9skcAQ2UbcyAeVA==", + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", "bin": { "yaml": "bin.mjs" }, diff --git a/NodeApp/package.json b/NodeApp/package.json index b6155d914200399fedf4c430cd13d65757a80cb9..52d1b78367af51a475b6666b85283bbfdec1ad09 100644 --- a/NodeApp/package.json +++ b/NodeApp/package.json @@ -1,9 +1,9 @@ { "name" : "dojo_cli", "description" : "CLI of the Dojo project", - "version" : "4.1.1", + "version" : "4.2.0", "license" : "AGPLv3", - "author" : "Michaël Minelli <dojo@minelli.me>", + "author" : "Michaël Minelli <dojo@mail.minelli.swiss>", "main" : "dist/app.js", "bin" : { "dojo": "./dist/app.js" @@ -29,12 +29,12 @@ "lint" : "npx eslint .", "genversion" : "npx genversion -s -e src/config/Version.ts", "build" : "npm run genversion; npx tsc", - "start:dev" : "npm run genversion; npm run lint; tsc --noEmit && npx tsx src/app.ts", + "start:dev" : "npm run genversion; npm run lint; tsc --noEmit && npx tsx --no-warnings src/app.ts", "test" : "echo \"Error: no test specified\" && exit 1" }, "dependencies" : { - "@dotenvx/dotenvx" : "^0.44.1", - "@eslint/js" : "^9.3.0", + "@dotenvx/dotenvx" : "^0.45.0", + "@eslint/js" : "^9.6.0", "@gitbeaker/core" : "^40.0.3", "@gitbeaker/requester-utils": "^40.0.3", "@gitbeaker/rest" : "^40.0.3", @@ -42,6 +42,7 @@ "axios" : "^1.7.2", "boxen" : "^5.1.2", "chalk" : "^4.1.2", + "cli-table3" : "^0.6.5", "commander" : "^12.1.0", "form-data" : "^4.0.0", "fs-extra" : "^11.2.0", @@ -55,7 +56,7 @@ "tar-stream" : "^3.1.7", "winston" : "^3.13.0", "winston-transport" : "^4.7.0", - "yaml" : "^2.4.2", + "yaml" : "^2.4.5", "zod" : "^3.23.8", "zod-validation-error" : "^3.3.0" }, @@ -63,18 +64,19 @@ "@types/fs-extra" : "^11.0.4", "@types/inquirer" : "^8.2.10", "@types/jsonwebtoken" : "^8.5.9", - "@types/node" : "^18.19.33", + "@types/node" : "^18.19.39", "@types/semver" : "^7.5.8", "@types/tar-stream" : "^3.1.3", - "@typescript-eslint/eslint-plugin": "^7.11.0", - "@typescript-eslint/parser" : "^7.11.0", - "dotenv-vault" : "^1.26.1", + "@typescript-eslint/eslint-plugin": "^7.15.0", + "@typescript-eslint/parser" : "^7.15.0", + "dotenv-cli" : "^7.4.2", + "dotenv-vault" : "^1.26.2", "eslint" : "^8.57.0", "genversion" : "^3.2.0", "pkg" : "^5.8.1", "tiny-typed-emitter" : "^2.1.0", - "tsx" : "^4.11.0", - "typescript" : "^5.4.5", - "typescript-eslint" : "^7.11.0" + "tsx" : "^4.16.2", + "typescript" : "^5.5.3", + "typescript-eslint" : "^7.15.0" } } diff --git a/NodeApp/src/commander/CommanderApp.ts b/NodeApp/src/commander/CommanderApp.ts index 7cb5952408f98e02b73517ca53746c41dc13c3ab..30e9cfe3ba8195af46113d33add1ecff63aa2550 100644 --- a/NodeApp/src/commander/CommanderApp.ts +++ b/NodeApp/src/commander/CommanderApp.ts @@ -13,6 +13,7 @@ import AuthCommand from './auth/AuthCommand.js'; import SessionCommand from './auth/SessionCommand.js'; import UpgradeCommand from './UpgradeCommand.js'; import TextStyle from '../types/TextStyle.js'; +import TagCommand from './tag/TagCommand'; class CommanderApp { @@ -118,6 +119,7 @@ ${ TextStyle.CODE(' dojo upgrade ') }`, { SessionCommand.registerOnCommand(this.program); AssignmentCommand.registerOnCommand(this.program); ExerciseCommand.registerOnCommand(this.program); + TagCommand.registerOnCommand(this.program); CompletionCommand.registerOnCommand(this.program); UpgradeCommand.registerOnCommand(this.program); } diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts index 922c126c5d7205de5de12136b80fcc980686ddcc..86a5bc585c2f68c833fb8b3618792da99fa9b439 100644 --- a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts +++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts @@ -33,9 +33,7 @@ class AssignmentCreateCommand extends CommanderCommand { private async dataRetrieval(options: CommandOptions) { console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); - if ( !await AccessesHelper.checkTeachingStaff() ) { - throw new Error(); - } + await AccessesHelper.checkTeachingStaff(); this.members = await GitlabManager.fetchMembers(options); if ( !this.members ) { diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts index 8089092754831b94bf8b9fba3e07e2fcd92a657e..02e40e36a7bd369b056c2cf5a8303e796d8af5f5 100644 --- a/NodeApp/src/commander/exercise/ExerciseCommand.ts +++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts @@ -2,6 +2,7 @@ import CommanderCommand from '../CommanderCommand.js'; import ExerciseCreateCommand from './subcommands/ExerciseCreateCommand.js'; import ExerciseRunCommand from './subcommands/ExerciseRunCommand.js'; import ExerciseCorrectionCommand from './subcommands/ExerciseCorrectionCommand.js'; +import ExerciseDeleteCommand from './subcommands/ExerciseDeleteCommand'; class ExerciseCommand extends CommanderCommand { @@ -15,6 +16,7 @@ class ExerciseCommand extends CommanderCommand { protected defineSubCommands() { ExerciseCreateCommand.registerOnCommand(this.command); ExerciseRunCommand.registerOnCommand(this.command); + ExerciseDeleteCommand.registerOnCommand(this.command); ExerciseCorrectionCommand.registerOnCommand(this.command); } diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts index 0a7aac7bb2302c1b1ec538803f4e87c1fb4d567e..650d1504f725c7db60d9cb5b45140838d21d5300 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCorrectionCommand.ts @@ -17,8 +17,8 @@ class ExerciseCorrectionCommand extends CommanderCommand { protected defineCommand() { this.command - .description('link an exercise repo as a correction for an assignment') - .requiredOption('-a, --assignment <string>', 'id or url of the assignment of the correction') + .description('list corrections of an assignment') + .requiredOption('-a, --assignment <string>', 'id or url of the assignment') .action(this.commandAction.bind(this)); } diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts index 73a67c5387e448c4e904808e9c4341cbde33c971..3db1510582c5927fcf506ac6acc360f04548aa8c 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts @@ -33,9 +33,7 @@ class ExerciseCreateCommand extends CommanderCommand { private async dataRetrieval(options: CommandOptions) { console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); - if ( !await AccessesHelper.checkStudent() ) { - throw new Error(); - } + await AccessesHelper.checkStudent(); this.members = await GitlabManager.fetchMembers(options); if ( !this.members ) { diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseDeleteCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseDeleteCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ec20cdfd13b93306de1bf9095adc9c6d40abb5c --- /dev/null +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseDeleteCommand.ts @@ -0,0 +1,38 @@ +import CommanderCommand from '../../CommanderCommand'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import AccessesHelper from '../../../helpers/AccessesHelper'; +import TextStyle from '../../../types/TextStyle'; + + +class ExerciseDeleteCommand extends CommanderCommand { + protected commandName: string = 'delete'; + + protected defineCommand(): void { + this.command + .description('delete an exercise') + .argument('id or url', 'id or url of the exercise') + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval() { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + await AccessesHelper.checkStudent(); + } + + private async deleteExercise(exerciseIdOrUrl: string) { + console.log(TextStyle.BLOCK('Please wait while we are deleting the exercise...')); + + await DojoBackendManager.deleteExercise(exerciseIdOrUrl); + } + + protected async commandAction(exerciseIdOrUrl: string): Promise<void> { + try { + await this.dataRetrieval(); + await this.deleteExercise(exerciseIdOrUrl); + } catch ( e ) { /* Do nothing */ } + } +} + + +export default new ExerciseDeleteCommand(); diff --git a/NodeApp/src/commander/tag/TagCommand.ts b/NodeApp/src/commander/tag/TagCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..7d0698aa10076825a6ba0548203cc315aaa1c4bc --- /dev/null +++ b/NodeApp/src/commander/tag/TagCommand.ts @@ -0,0 +1,27 @@ +import CommanderCommand from '../CommanderCommand'; +import TagCreateCommand from './subcommands/TagCreateCommand'; +import TagDelete from './subcommands/TagDeleteCommand'; +import TagProposalCommand from './subcommands/proposal/TagProposalCommand'; + + +class TagCommand extends CommanderCommand { + protected commandName: string = 'tag'; + + protected defineCommand() { + this.command + .description('manage tags'); + } + + protected defineSubCommands() { + TagCreateCommand.registerOnCommand(this.command); + TagDelete.registerOnCommand(this.command); + TagProposalCommand.registerOnCommand(this.command); + } + + protected async commandAction(): Promise<void> { + // No action + } +} + + +export default new TagCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/TagCreateCommand.ts b/NodeApp/src/commander/tag/subcommands/TagCreateCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..fcbf63e76e94c741e2f08c1f0f2603d9cdd29990 --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/TagCreateCommand.ts @@ -0,0 +1,60 @@ +import CommanderCommand from '../../CommanderCommand'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import { Option } from 'commander'; +import TextStyle from '../../../types/TextStyle'; +import SessionManager from '../../../managers/SessionManager'; +import ora from 'ora'; + + +type CommandOptions = { name: string, type: 'Language' | 'Framework' | 'Theme' | 'UserDefined' } + + +class TagCreateCommand extends CommanderCommand { + protected commandName: string = 'create'; + + protected defineCommand() { + this.command + .description('create a new tag') + .requiredOption('-n, --name <name>', 'name of the tag') + .addOption(new Option('-t, --type <type>', 'type of the tag').choices([ 'Language', 'Framework', 'Theme', 'UserDefined' ]).makeOptionMandatory(true)) + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval(options: CommandOptions) { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + const sessionResult = await SessionManager.testSession(true, [ 'admin' ]); + + if ( !sessionResult ) { + throw new Error(); + } + + if ( options.type !== 'UserDefined' && !sessionResult.admin ) { + ora({ + text : `Only admins can create non UserDefined tags`, + indent: 4 + }).start().fail(); + throw new Error(); + } + } + + private async createTag(options: CommandOptions) { + console.log(TextStyle.BLOCK('Please wait while we are creating the tag...')); + + const tag = await DojoBackendManager.createTag(options.name, options.type); + if ( !tag ) { + throw new Error(); + } + } + + protected async commandAction(options: CommandOptions): Promise<void> { + try { + await this.dataRetrieval(options); + await this.createTag(options); + } catch ( e ) { /* Do nothing */ } + } + +} + + +export default new TagCreateCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/TagDeleteCommand.ts b/NodeApp/src/commander/tag/subcommands/TagDeleteCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..dae2d18a27f8dc3d060e94678781d4b6b44bb370 --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/TagDeleteCommand.ts @@ -0,0 +1,40 @@ +import CommanderCommand from '../../CommanderCommand'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import TextStyle from '../../../types/TextStyle'; +import AccessesHelper from '../../../helpers/AccessesHelper'; + + +class TagDeleteCommand extends CommanderCommand { + protected commandName: string = 'delete'; + + protected defineCommand() { + this.command + .description('Delete a tag') + .argument('<name>', 'name of the tag') + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval() { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + await AccessesHelper.checkAdmin(); + } + + private async deleteTag(name: string) { + console.log(TextStyle.BLOCK('Please wait while we are deleting the tag...')); + + if ( !await DojoBackendManager.deleteTag(name) ) { + throw new Error(); + } + } + + protected async commandAction(name: string): Promise<void> { + try { + await this.dataRetrieval(); + await this.deleteTag(name); + } catch ( e ) { /* Do nothing */ } + } +} + + +export default new TagDeleteCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/TagProposalCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/TagProposalCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..304a64733e7cd63947c7e9dc6cd2d4861d1dd28d --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/TagProposalCommand.ts @@ -0,0 +1,28 @@ +import CommanderCommand from '../../../CommanderCommand'; +import TagProposalListCommand from './subcommands/TagProposalListCommand'; +import TagProposalCreateCommand from './subcommands/TagProposalCreateCommand'; +import TagProposalApproveCommand from './subcommands/TagProposalApproveCommand'; +import TagProposalDeclineCommand from './subcommands/TagProposalDeclineCommand'; + + +class TagProposalCommand extends CommanderCommand { + protected commandName: string = 'proposal'; + + protected defineCommand() { + this.command.description('manage tag proposals'); + } + + protected defineSubCommands() { + TagProposalListCommand.registerOnCommand(this.command); + TagProposalCreateCommand.registerOnCommand(this.command); + TagProposalApproveCommand.registerOnCommand(this.command); + TagProposalDeclineCommand.registerOnCommand(this.command); + } + + protected async commandAction(): Promise<void> { + // No action + } +} + + +export default new TagProposalCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalAnswerCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalAnswerCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..b0b1523d754264c62179398b94070974ab8e94bf --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalAnswerCommand.ts @@ -0,0 +1,44 @@ +import CommanderCommand from '../../../../CommanderCommand'; +import DojoBackendManager from '../../../../../managers/DojoBackendManager'; +import TextStyle from '../../../../../types/TextStyle'; +import AccessesHelper from '../../../../../helpers/AccessesHelper'; + + +type CommandOptions = { commentary?: string } + + +abstract class TagProposalAnswerCommand extends CommanderCommand { + protected abstract state: 'Approved' | 'Declined'; + + protected defineCommand() { + this.command + .description(`${ this.state === 'Approved' ? 'Approve' : 'Decline' } a tag proposition`) + .argument('<name>', 'name of the tag proposition') + .option('-c, --commentary <comment>', 'add a commentary to the answer') + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval() { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + await AccessesHelper.checkAdmin(); + } + + private async answerTag(name: string, options: CommandOptions) { + console.log(TextStyle.BLOCK('Please wait while we are answering to the tag proposal...')); + + if ( !await DojoBackendManager.answerTagProposal(name, this.state, options.commentary ?? '') ) { + throw new Error(); + } + } + + protected async commandAction(name: string, options: CommandOptions): Promise<void> { + try { + await this.dataRetrieval(); + await this.answerTag(name, options); + } catch ( e ) { /* Do nothing */ } + } +} + + +export default TagProposalAnswerCommand; \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalApproveCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalApproveCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..e97b32310dfc84065f54680c383ee693b55f08d9 --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalApproveCommand.ts @@ -0,0 +1,10 @@ +import TagProposalAnswerCommand from './TagProposalAnswerCommand'; + + +class TagProposalApproveCommand extends TagProposalAnswerCommand { + protected commandName: string = 'approve'; + protected state: 'Approved' | 'Declined' = 'Approved'; +} + + +export default new TagProposalApproveCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalCreateCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalCreateCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..d34a4bc9d20ffffcc1962b976bb5bf5c5983d8d7 --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalCreateCommand.ts @@ -0,0 +1,46 @@ +import CommanderCommand from '../../../../CommanderCommand'; +import DojoBackendManager from '../../../../../managers/DojoBackendManager'; +import { Option } from 'commander'; +import TextStyle from '../../../../../types/TextStyle'; +import AccessesHelper from '../../../../../helpers/AccessesHelper'; + + +type CommandOptions = { name: string, type: 'Language' | 'Framework' | 'Theme' | 'UserDefined' } + + +class TagProposalCreateCommand extends CommanderCommand { + protected commandName: string = 'create'; + + protected defineCommand() { + this.command + .description('Propose a new tag') + .requiredOption('-n, --name <name>', 'name of the tag') + .addOption(new Option('-t, --type <type>', 'type of the tag').choices([ 'Language', 'Framework', 'Theme', 'UserDefined' ]).makeOptionMandatory(true)) + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval() { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + await AccessesHelper.checkTeachingStaff(); + } + + private async createTagProposal(options: CommandOptions) { + console.log(TextStyle.BLOCK('Please wait while we are creating the tag proposal...')); + + const tag = await DojoBackendManager.createTagProposal(options.name, options.type); + if ( !tag ) { + throw new Error(); + } + } + + protected async commandAction(options: CommandOptions): Promise<void> { + try { + await this.dataRetrieval(); + await this.createTagProposal(options); + } catch ( e ) { /* Do nothing */ } + } +} + + +export default new TagProposalCreateCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalDeclineCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalDeclineCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..aae1ccc120b1011cc96a0f0429b806574d6508de --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalDeclineCommand.ts @@ -0,0 +1,10 @@ +import TagProposalAnswerCommand from './TagProposalAnswerCommand'; + + +class TagProposalDeclineCommand extends TagProposalAnswerCommand { + protected commandName: string = 'decline'; + protected state: 'Approved' | 'Declined' = 'Declined'; +} + + +export default new TagProposalDeclineCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalListCommand.ts b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalListCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..f73bbd0caaf0d7ed01e35d02f419df7b9b90d934 --- /dev/null +++ b/NodeApp/src/commander/tag/subcommands/proposal/subcommands/TagProposalListCommand.ts @@ -0,0 +1,68 @@ +import CommanderCommand from '../../../../CommanderCommand'; +import TagProposal from '../../../../../sharedByClients/models/TagProposal'; +import DojoBackendManager from '../../../../../managers/DojoBackendManager'; +import TextStyle from '../../../../../types/TextStyle'; +import AccessesHelper from '../../../../../helpers/AccessesHelper'; +import { Option } from 'commander'; +import ora from 'ora'; +import Table from 'cli-table3'; + + +type CommandOptions = { state: 'PendingApproval' | 'Approved' | 'Declined' } + + +class TagProposalListCommand extends CommanderCommand { + protected commandName: string = 'list'; + + protected defineCommand() { + this.command + .description('Get a tag proposition') + .addOption(new Option('-s, --state <state>', 'state of the tag proposal').choices([ 'PendingApproval', 'Approved', 'Declined' ]).default('PendingApproval')) + .action(this.commandAction.bind(this)); + } + + private async dataRetrieval() { + console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...')); + + await AccessesHelper.checkAdmin(); + } + + private async listTagProposals(options: CommandOptions) { + console.log(TextStyle.BLOCK('Please wait while we are creating the tag...')); + + const spinner: ora.Ora = ora('Retrieving tag proposals...'); + + const tags = await DojoBackendManager.getTagProposals(options.state); + + if ( tags && tags.length > 0 ) { + spinner.succeed(`Tag proposals retrieved. Here is the list of tag proposal with '${ options.state }' state:`); + + const table = new Table({ + head: [ 'Name', 'Type', 'Details' ] + }); + + tags.forEach((tag: TagProposal) => { + table.push([ tag.name, tag.type, tag.details ]); + }); + + console.log(table.toString()); + } else if ( tags ) { + spinner.fail(`There is no tag proposal with '${ options.state }' state.`); + throw new Error(); + } else { + spinner.fail('Failed to retrieve tag proposals.'); + throw new Error(); + } + } + + protected async commandAction(options: CommandOptions): Promise<void> { + try { + await this.dataRetrieval(); + await this.listTagProposals(options); + } catch ( e ) { /* Do nothing */ } + } + +} + + +export default new TagProposalListCommand(); \ No newline at end of file diff --git a/NodeApp/src/helpers/AccessesHelper.ts b/NodeApp/src/helpers/AccessesHelper.ts index 1be0777e85227aae8e950cb6974e5df2866e72ba..a07b7aa24a40229be5460f1126e0e99eb4455138 100644 --- a/NodeApp/src/helpers/AccessesHelper.ts +++ b/NodeApp/src/helpers/AccessesHelper.ts @@ -3,32 +3,28 @@ import GitlabManager from '../managers/GitlabManager.js'; class AccessesHelper { - async checkStudent(testGitlab: boolean = false): Promise<boolean> { - const sessionResult = await SessionManager.testSession(true, [ 'student' ]); + private async checkAccess(accessName: string, testGitlab: boolean = false) { + const sessionResult = await SessionManager.testSession(true, [ accessName ]); - if ( !sessionResult ) { - return false; + if ( !sessionResult || !(sessionResult as unknown as { [key: string]: boolean })[accessName] ) { + throw new Error(); } - if ( testGitlab ) { - return (await GitlabManager.testToken(true)).every(result => result); - } else { - return true; + if ( testGitlab && !(await GitlabManager.testToken(true)).every(result => result) ) { + throw new Error(); } } - async checkTeachingStaff(testGitlab: boolean = false): Promise<boolean> { - const sessionResult = await SessionManager.testSession(true, [ 'teachingStaff' ]); + async checkStudent(testGitlab: boolean = false) { + await this.checkAccess('student', testGitlab); + } - if ( !sessionResult || !sessionResult.teachingStaff ) { - return false; - } + async checkTeachingStaff(testGitlab: boolean = false) { + await this.checkAccess('teachingStaff', testGitlab); + } - if ( testGitlab ) { - return (await GitlabManager.testToken(true)).every(result => result); - } else { - return true; - } + async checkAdmin(testGitlab: boolean = false) { + await this.checkAccess('admin', testGitlab); } } diff --git a/NodeApp/src/managers/DojoBackendManager.ts b/NodeApp/src/managers/DojoBackendManager.ts index 8c8a5a9685c1ab1fb2cabbfc4740f2914b54beae..0a878367581dbd7022567590557cf88222c05d00 100644 --- a/NodeApp/src/managers/DojoBackendManager.ts +++ b/NodeApp/src/managers/DojoBackendManager.ts @@ -11,6 +11,9 @@ import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode.js'; import * as Gitlab from '@gitbeaker/rest'; import DojoBackendHelper from '../sharedByClients/helpers/Dojo/DojoBackendHelper.js'; import GitlabPipelineStatus from '../shared/types/Gitlab/GitlabPipelineStatus.js'; +import Tag from '../sharedByClients/models/Tag'; +import TagProposal from '../sharedByClients/models/TagProposal'; +import Result from '../sharedByClients/models/Result'; class DojoBackendManager { @@ -59,6 +62,15 @@ class DojoBackendManager { case DojoStatusCode.GITLAB_TEMPLATE_ACCESS_UNAUTHORIZED: spinner.fail(`Please check that the template have public/internal visibility or that your and Dojo account (${ ClientsSharedConfig.gitlab.dojoAccount.username }) have at least reporter role to the template (if private).`); break; + case DojoStatusCode.TAG_ONLY_ADMIN_CREATION: + spinner.fail(`Only admins can create non UserDefined tags.`); + break; + case DojoStatusCode.TAG_WITH_ACTIVE_LINK_DELETION: + spinner.fail(`This tag is used in resources (e.g. assignments). Please remove this tag from these resources before deleting it.`); + break; + case DojoStatusCode.TAG_PROPOSAL_ANSWER_NOT_PENDING: + spinner.fail(`This tag proposal have already been answered.`); + break; default: if ( otherErrorHandler ) { otherErrorHandler(error, spinner, verbose); @@ -73,7 +85,6 @@ class DojoBackendManager { } } - public async login(gitlabTokens: GitlabToken): Promise<User | undefined> { try { return (await axios.post<DojoBackendResponse<User>>(DojoBackendHelper.getApiUrl(ApiRoute.LOGIN), { @@ -249,6 +260,190 @@ class DojoBackendManager { return false; } } + + public async createTag(name: string, type: string, verbose: boolean = true): Promise<Tag | undefined> { + const spinner: ora.Ora = ora('Creating tag...'); + + if ( verbose ) { + spinner.start(); + } + + try { + const response = await axios.post<DojoBackendResponse<Tag>>(DojoBackendHelper.getApiUrl(ApiRoute.TAG_CREATE), { + name: name, + type: type + }); + + if ( verbose ) { + spinner.succeed(`Tag successfully created`); + } + + return response.data.data; + } catch ( error ) { + this.handleApiError(error, spinner, verbose, `Tag creation error: ${ error }`); + + return undefined; + } + } + + public async deleteTag(name: string, verbose: boolean = true): Promise<boolean> { + const spinner: ora.Ora = ora('Deleting tag...'); + + if ( verbose ) { + spinner.start(); + } + + try { + await axios.delete<DojoBackendResponse<Tag>>(DojoBackendHelper.getApiUrl(ApiRoute.TAG_DELETE, { tagName: name })); + + if ( verbose ) { + spinner.succeed(`Tag successfully deleted`); + } + + return true; + } catch ( error ) { + this.handleApiError(error, spinner, verbose, `Tag deletion error: ${ error }`); + + return false; + } + } + + public async getTagProposals(state: string | undefined): Promise<Array<TagProposal> | undefined> { + try { + return (await axios.get<DojoBackendResponse<Array<TagProposal>>>(DojoBackendHelper.getApiUrl(ApiRoute.TAG_PROPOSAL_GET_CREATE), { params: { stateFilter: state } })).data.data; + } catch ( error ) { + return undefined; + } + } + + public async createTagProposal(name: string, type: string, verbose: boolean = true): Promise<TagProposal | undefined> { + const spinner: ora.Ora = ora('Creating tag...'); + + if ( verbose ) { + spinner.start(); + } + + try { + const response = await axios.post<DojoBackendResponse<TagProposal>>(DojoBackendHelper.getApiUrl(ApiRoute.TAG_PROPOSAL_GET_CREATE), { + name: name, + type: type + }); + + if ( verbose ) { + spinner.succeed(`Tag proposal successfully created`); + } + + return response.data.data; + } catch ( error ) { + this.handleApiError(error, spinner, verbose, `Tag proposal creation error: ${ error }`); + + return undefined; + } + } + + public async answerTagProposal(tagProposalName: string, state: 'Approved' | 'Declined', details: string, verbose: boolean = true): Promise<boolean> { + const spinner: ora.Ora = ora('Answering tag proposal...'); + + if ( verbose ) { + spinner.start(); + } + + try { + await axios.patch<DojoBackendResponse<TagProposal>>(DojoBackendHelper.getApiUrl(ApiRoute.TAG_PROPOSAL_UPDATE, { tagName: tagProposalName }), { + state : state, + details: details + }); + + if ( verbose ) { + spinner.succeed(`Tag proposal ${ state.toLowerCase() } with success`); + } + + return true; + } catch ( error ) { + this.handleApiError(error, spinner, verbose, `Tag proposal answer error: ${ error }`); + + return false; + } + } + + public async getUserExercises(): Promise<Array<Exercise> | undefined> { + try { + const response = await axios.get<DojoBackendResponse<Array<Exercise>>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_LIST)); + return response.data.data; + } catch ( error ) { + console.error('Error fetching user exercises:', error); + return undefined; + } + } + + public async getExerciseDetails(exerciseIdOrUrl: string): Promise<Exercise | undefined> { + try { + + const response = await axios.get<Exercise>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_DETAILS_GET, { + exerciseIdOrUrl: exerciseIdOrUrl + })); + return response.data; + } catch ( error ) { + console.error('Error fetching exercise details:', error); + return undefined; + } + } + + public async deleteExercise(exerciseIdOrUrl: string, verbose: boolean = true): Promise<void> { + const spinner: ora.Ora = ora('Deleting exercise...'); + + if ( verbose ) { + spinner.start(); + } + + try { + await axios.delete<DojoBackendResponse<Exercise>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_GET_DELETE, { + exerciseIdOrUrl: exerciseIdOrUrl + })); + + if ( verbose ) { + spinner.succeed(`Exercise deleted with success`); + } + } catch ( error ) { + this.handleApiError(error, spinner, verbose, `Exercise deleting error: ${ error }`); + + throw error; + } + } + + public async getExerciseMembers(exerciseIdOrUrl: string): Promise<Array<User>> { + return (await axios.get<DojoBackendResponse<Array<User>>>(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_MEMBERS_GET, { + exerciseIdOrUrl: exerciseIdOrUrl + }))).data.data; + } + + public async getExerciseResults(exerciseIdOrUrl: string): Promise<Array<Result>> { + try { + const response = await axios.get(DojoBackendHelper.getApiUrl(ApiRoute.EXERCISE_RESULTS, { + exerciseIdOrUrl: exerciseIdOrUrl + })); + + return response.data as Array<Result>; + } catch ( error ) { + console.error('Error fetching exercise results:', error); + return []; + } + } + + public async getUsers(roleFilter?: string): Promise<Array<User> | undefined> { + try { + const response = await axios.get<DojoBackendResponse<Array<User>>>(DojoBackendHelper.getApiUrl(ApiRoute.USER_LIST), { params: roleFilter ? { roleFilter: roleFilter } : {} }); + + return response.data.data; + } catch ( error ) { + console.error('Error fetching professors:', error); + return undefined; + } + } + + public async getTeachers(): Promise<Array<User> | undefined> { + return this.getUsers('teacher'); + } } diff --git a/NodeApp/src/shared b/NodeApp/src/shared index c2afa861bf6306ddec79ffd465a4c7b0edcd3453..bf75a99ba472386daa111c2fefbe69a4272ef48c 160000 --- a/NodeApp/src/shared +++ b/NodeApp/src/shared @@ -1 +1 @@ -Subproject commit c2afa861bf6306ddec79ffd465a4c7b0edcd3453 +Subproject commit bf75a99ba472386daa111c2fefbe69a4272ef48c diff --git a/NodeApp/src/sharedByClients b/NodeApp/src/sharedByClients index 55a94e77db69635e1ca837a52de29cb04d0b4138..4e33e70f6035898f119369ae5db784d51d8298a0 160000 --- a/NodeApp/src/sharedByClients +++ b/NodeApp/src/sharedByClients @@ -1 +1 @@ -Subproject commit 55a94e77db69635e1ca837a52de29cb04d0b4138 +Subproject commit 4e33e70f6035898f119369ae5db784d51d8298a0