diff --git a/.gitignore b/.gitignore index a5cafce8aaff50784bfa58d9c64c38392092c4e4..bbcf40c9751719662c0912bd3f5b52dcc6e3e6ac 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ workspace.xml .gitlab-ci-local Wiki/.idea +NodeApp/src/config/Version.ts + ############################ MacOS # General .DS_Store diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bc7131614fb2ae150155b19cdc4dd631d137e12..a4d0f582d4aa88849532df7fc7799d85b497d7dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,29 @@ **β οΈ Deprecation:** --> -## 2.1.0 (?) +## 2.2.0 (2023-10-16) + +### β¨ Feature +- `results.json` file is now optional (if the teaching staff don't want to provide test details) + - The exercise will be considered as valid if the container exit code is 0 + - The `results.json` file will be construct / completed with the container exit code + - The `volume` argument of `dojo_assignment.json` is now optional (if the teaching staff don't want to provide `results.json` file or other files) +- Assignment run command added (to run the assignment locally) + +### π€ Minor change +- Immutable files are added to the gitignore file of newly created exercises. + +### π¨ Interface +- The gitlab token can be passed as secret user input in addition to the command line. + +### π¨ Internal / Developers +- Environment support added to CLI config file + +### π Documentation +- Wiki update for this new version + + +## 2.1.0 (2023-09-29) ### β¨ Feature - Added pipeline badge to exercises diff --git a/NodeApp/.env.vault b/NodeApp/.env.vault index f6d35a5f86ea190e012c9ec8d378bab125ef1e2f..08c024706a6acbd81885bf354454c5ddc6d6c4ea 100644 --- a/NodeApp/.env.vault +++ b/NodeApp/.env.vault @@ -4,8 +4,8 @@ #/--------------------------------------------------/ # development -DOTENV_VAULT_DEVELOPMENT="McsEkttfKr1Vl0q9g7z4+6IRodJFBiT+rZWlz8SH12VCeviOBsot3YnUku80PwUxI3MqaWKmw6WNL5eLxKLlo64coMfgdajaTcp9yOfK6CRfHfWdh3MCoAf8ZiqbJGkmaH2X8j2QcnzJ6C+v8S1ZPUxk49ug+6otlyKl8hnr9ECvNGFKJ+Xa5L7XzEo+IGIFDrEYDtxI2VyUOiwudRnP0WarU8KJNt28IZYz3M5zymdFFI8Wlcq2+KiOp/CjiKYDxALkcINGkffu1WIU/lS0WE1FVLbds2hMsGkB4LwT1Q1ASkmsVT/BIb/wRxamDmAN494cTgGqYdaM5IOCGt2BDtm2UJWFrM5txl/PKRrbgE8wjpIaSp5OQ33nRV2P1fGk38z3gYHVI3NG5SRzxZTo+D4lT9UdNmwWIzL6rXRfnJgUTwKhc0MBrl+giaIt3U8wzYB69/F5+thYruSJVir42/b+XpOWSovZTwYYcAO5dXML/24jR3eNMxTlcxVvXpdx/Eg8r5ZCHSsMVKCgmyfGA49xWGjaMqOiizu83mt9KhsXiRgaI1imKiOm85eK9CUkvfLTQo8ccK+RITMvLKtCZgirF4HLi7y5DVjvufhtjxxOXjknFmI3UJFmjU9cUAKC3ccsM9F8Pm4mo6XbcPjBXZdX/18gtlvxps5at1cH998F8ZPkoaEJGp7lCYlxXTEBHOxxCEd5DdUldhpbOUfSsYZHUuxiA1gqJvilok39bgxl1WpO6nPM7S0S0GaI1jGkPKOJ/gx1fBRY91sSQy9Qw/O3tBUId1Az0QZLQZ+gijr6kchd5aczHmIRa2z4AHCSbeuzmaGfP+4yh0M6FvPPCD4i" +DOTENV_VAULT_DEVELOPMENT="FvCrekiDgv/1bvov01DTRTEdipq7tduoYf/cAjX3+slE4zZSVG5S7AuQ2bXf4gzSHrfxjsx2kTl5ild1l+pSQGiDrLqdj0EigMleCya/xRiEDuVBeGRLZLJfWS/AGYi31/sNxtCT2FBcYoEZ+6KF0pq7jyMNTwaFFoSZgTBd+kk3GG1iF36pxt+lGHyeI6TOQQtFFgyoVzxLLZeBQp0V2OJAltVvXnCHzgKyY4zyHb5teyNVEKa2HOf5UivnWJqk89HSD8c+ijvS8Jq8JRu7nzI81JJQwGmU3MNUyRkt+jVJxv0H67YU5Cw18Fqa3NEb8K9mDeHJImwfU2HGhv8WZzqWEXKC/so1hjO6qWOHxwWPqko5YYNPOQw0vPwEzXZPcY/OSH70gHYqkJl3zOA1GwdHY1jBmwdPWOu6GSekxPED8gDrtW6x0TsvdSfLGRODDTIXJVCKM3xkkkV7oU+4hO743HwrVQxvyWrLQucPFSMQVEe+XA0kUddNGcgj9A3VfpWJwm2NycQXqYWuOEX0Q3qyLVi75KvZsPr8vBR3EcZfWtYGfC/2a8rl+kAZ9w8wpBoca6F8nnFkh7deB+/fV5uMRSDiEHI+aJufDFcOXq4J+uljQRDlCo8trrRkcqkYLJWg/ctvDWBG7A6ODgWLk31ycCryj/w297UsE7mew84uA5eCFQIbO3PT9MIKMy0hK3pFkoQq3KhL9S9/dl8VtvvyfNu1zfPyNl6ckiBJz0zG7zIV5sxGUcSdClUBluaQBxdU1kA2kkoAYrPTX56XaC9ZLx9VvbloWRAJxMwxnQV6w1vxWxRgtV3tzlZBK2JyWtEu+cykbnLJM4cfhP/7bysZqnidOuwv7cZy33plIFqzXxOiujCxpShI3IoALnx+nRo/vS4QvLJwU9Lh5QsSuKzHXoheobCpqgZM2aflNPmFdUPAgCqXJ5caEjRSdqlGf/lFE84qrwl3XB8rU4Emn1jIK7SwlcNbk4fVq6BccOIzS0tZzB4EPZNg6YYO8jfRNVhr2On8WOAwtVH9Jikvq2ffD88GQMZu+d/YDLwAmgg4eIjQscNHPvERyjlhyKE5" # production -DOTENV_VAULT_PRODUCTION="aWWYhhCla0zC0MtLgZl1O7b4j8q5ya47bbHgZZtNJzw1PrL1I/2axdy6atW3v1cstw2putaNl4Gc81IM3GUPHyP6dDod6L7AMmLXKwrQFoYUxz0Za8fWLpnx79nY6A/zNgwfBqEZ0NE5o/GFKCDRYsfA6UuX8f6jBN2lhVK9leMU5DRvEyPrhKV4KHDwjKSTuqNbkZNW/BwyTf9Fdrd/SgUjgWA5Rvrg6N3RSkcHsuHfrBXzuzFHUFjQyrDlfS+fmpHj6PN69HWwtG+OjpgPaLocZ3WN2cDnb9X+rhB1W0uTN9Y8gAJkddUw3Kynf7YXimEBxWwV3ltdftIPAIcnz5cOx6lbzwFrXPnl2FesjpFZ1G27rZVr61kSUiILjRbOg9KIEmSqJXFzUHEyX7FwEYWxHtm1jw+76ePTV5g+4yfIxx2Vjo78uUKreASwtlA6CnsnnkFKqqLE3LYtskI7SYq/2t/Y0UrFKOFrOiK3Y5Kza7rRfcZo5SoNGFcyBw+4/YV5Tk/8z4zhyt/6lzLTA/w/mNXv4KCKn2oRBIUOlaMClvfmA2pwCv7YgpCGAycj0qQkhNhhIkd2F3cuc0o+U1XnWiqlEIVX9Z40gnrzodTRBmdvjVNdQ79nMvEVsmzjwmhoAC62RnyEnMUBrE9XM3RvRFoYFThOd7RE5TxCmLjbZfyVYN58hufZZDFT3oPYzoT9rzur6nU0NqjfNNU6BfZK4L+qGBg=" +DOTENV_VAULT_PRODUCTION="XJI0mhrbeefm0pw+Ii2qnr8DLtnxVYRAt+jdzIudrQmrepJBuj7ujWT3+e4Fydw5zgqQeSZHLolSNV/hCh97cNmTd4+vEipb6pfoEPlTDoiv869kcOW9oJ2St5RcWK4ZtTTlJNXqD9AWp5ST+Ox83SUsGjZpqTdz7pN9YnnlnoWaCDxnWxPI25CqKfwI66ALvbZ3/GYplT8nWC9cVll9UnwgpFF8ol+AXO57cccLz1dzEjGI4bODbrjhxO9ZkgNbVYHpemY10hV7BeVsSR5wwuxXI8B0cG4jhlXX6Uf/GnGSJ++4LsIraA7FnNmtBpxIXnsImU6G4j3ILJvtnjhl6aIL39zenlZep7ZeTQAtQPhHUmS/tF9xPBqFSUTghE+ZrLOHnqvoXl1/n4ynPCz20VzJtQ4MaoxaO4qOoktyMXU9c1YpqyG7qfLD3N2z6DK3jnIz8RhAhYimKpRb9QuH8gbKHu4BsQlNrl1VQz78sFo9XxtrrxLIOQgMNoANtqROfiVQdcpl4MdK/H3iPpPI/H2esovjjuL+h42tDLbjPq7H2ghjHzckK14ZwO20zyStK9hRY5fAfeO61v6n5EOrsFsjbO/6tIuyG0JQniUnDFWtyxFq78i8F/hom6UbJunHvyV69uSfqdST61vAU/yFtgBZ0WosTUQB7aBJ5nkRRfliZhYu/UCAJ/nJDtjnKgv0kbl0KmTMDTa3HeOfDpK6Yd53IrvPxgfaTkALB0yfAm6W9vGdV4675aLBIJ8A7PjdLz+Ydw6QmDWUROe+XGXGDKlQe2YGBdjT9fO9qEf7Hiv68xizdh3XYdkbr6cj8YDDI84ZpswoUjhVnzEb5erg1OIgHFAYtVV9jdmI8N+c426QcyEOXXSQI1/AM0rDKitdftZZahiaCRKtHDs1Zfnn+LulOKaJP7abgYlPYFkj7LpDVUaPZqo34uMbWatoltQpUbGIYGc=" diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json index 6d9c32798aafb2306f209eb808b9ce5398711637..38eccb9553557d7eef62152401c803a1503e3983 100644 --- a/NodeApp/package-lock.json +++ b/NodeApp/package-lock.json @@ -1,12 +1,13 @@ { "name": "dojo_cli", - "version": "2.1.0", + "version": "2.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dojo_cli", - "version": "2.1.0", + "version": "2.2.0", + "license": "AGPLv3", "dependencies": { "ajv": "^8.12.0", "appdata-path": "^1.0.0", @@ -15,6 +16,7 @@ "chalk": "^4.1.2", "commander": "^11.0.0", "dotenv": "^16.3.1", + "dotenv-expand": "^10.0.0", "fs-extra": "^11.1.1", "http-status-codes": "^2.2.0", "inquirer": "^8.2.5", @@ -22,7 +24,8 @@ "jsonwebtoken": "^8.5.1", "ora": "^5.4.1", "tar-stream": "^3.1.6", - "winston": "^3.10.0" + "winston": "^3.10.0", + "yaml": "^2.3.2" }, "bin": { "dojo": "dist/app.js" @@ -34,6 +37,7 @@ "@types/node": "^18.17.2", "@types/tar-stream": "^2.2.2", "dotenv-vault": "^1.25.0", + "genversion": "^3.1.1", "pkg": "^5.8.1", "tiny-typed-emitter": "^2.1.0", "ts-node": "^10.9.1", @@ -64,9 +68,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -153,9 +157,9 @@ } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", "dev": true, "engines": { "node": ">=6.0.0" @@ -177,21 +181,15 @@ "dev": true }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.19", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz", + "integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==", "dev": true, "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -281,21 +279,6 @@ "node": ">=10" } }, - "node_modules/@oclif/core/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@oclif/core/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -318,9 +301,9 @@ "dev": true }, "node_modules/@oclif/plugin-help": { - "version": "5.2.19", - "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.2.19.tgz", - "integrity": "sha512-gf6/dFtzMJ8RA4ovlBCBGJsZsd4jPXhYWJho+Gh6KmA+Ev9LupoExbE0qT+a2uHJyHEvIg4uX/MBW3qdERD/8g==", + "version": "5.2.20", + "resolved": "https://registry.npmjs.org/@oclif/plugin-help/-/plugin-help-5.2.20.tgz", + "integrity": "sha512-u+GXX/KAGL9S10LxAwNUaWdzbEBARJ92ogmM7g3gDVud2HioCmvWQCDohNRVZ9GYV9oKwZ/M8xwd6a1d95rEKQ==", "dev": true, "dependencies": { "@oclif/core": "^2.15.0" @@ -384,9 +367,9 @@ } }, "node_modules/@oclif/plugin-not-found": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-2.4.1.tgz", - "integrity": "sha512-LqW7qpw5Q8ploRiup2jEIMQJXcxHP1tpwj45GApKQMe7GRdGdRdjBT9Tu+U2tdEgMqgMplAIhOsYCx2nc2nMSw==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/@oclif/plugin-not-found/-/plugin-not-found-2.4.3.tgz", + "integrity": "sha512-nIyaR4y692frwh7wIHZ3fb+2L6XEecQwRDIb4zbEam0TvaVmBQWZoColQyWA84ljFBPZ8XWiQyTz+ixSwdRkqg==", "dev": true, "dependencies": { "@oclif/core": "^2.15.0", @@ -452,9 +435,9 @@ } }, "node_modules/@oclif/plugin-update": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/@oclif/plugin-update/-/plugin-update-3.2.3.tgz", - "integrity": "sha512-JVKwp4ysG9GU4RmG59MZYMunz8onRI+wEQzJThyYkUFd0VfZviYt2FHsyoNtxi30l0tInC8APgKp1pCCO4e+FQ==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@oclif/plugin-update/-/plugin-update-3.2.4.tgz", + "integrity": "sha512-41G7NTKND+yTpb8LHlvlMIcNoaEUIIJuEwju9igL+ME/pN/53opeXgFV2IjjeFiexXj50OfesY9OQ6lqOZHw+g==", "dev": true, "dependencies": { "@oclif/core": "^2.11.8", @@ -528,21 +511,6 @@ "node": ">=10" } }, - "node_modules/@oclif/plugin-update/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@oclif/plugin-update/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -559,9 +527,9 @@ } }, "node_modules/@oclif/plugin-warn-if-update-available": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.1.0.tgz", - "integrity": "sha512-liTWd/qSIqALsikr88CAB9o2xGFt0LdT5REbhxtrx16/trRmkxQ+0RHK1FieGZAzEENx/4D3YcC/Y67a0uyO0g==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@oclif/plugin-warn-if-update-available/-/plugin-warn-if-update-available-2.1.1.tgz", + "integrity": "sha512-y7eSzT6R5bmTIJbiMMXgOlbBpcWXGlVhNeQJBLBCCy1+90Wbjyqf6uvY0i2WcO4sh/THTJ20qCW80j3XUlgDTA==", "dev": true, "dependencies": { "@oclif/core": "^2.15.0", @@ -614,21 +582,6 @@ "node": ">=14.0.0" } }, - "node_modules/@oclif/plugin-warn-if-update-available/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@oclif/plugin-warn-if-update-available/node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -645,9 +598,9 @@ } }, "node_modules/@oclif/screen": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.6.tgz", - "integrity": "sha512-nEv7dFPxCrWrvK6dQ8zya0/Kb54EXVcwIKV9capjSa89ZDoOo+qH0YSo4/eQVECXgW3eUvgKLDIcIt62YBk0HA==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@oclif/screen/-/screen-3.0.7.tgz", + "integrity": "sha512-jQBPHcMh5rcIPKdqA6xlzioLOmkaVnjg2MVyjMzBKV8hDhLWNSiZqx7NAWXpP70v2LFvGdVoV8BSbK9iID3eHg==", "dev": true, "engines": { "node": ">=12.0.0" @@ -678,18 +631,18 @@ "dev": true }, "node_modules/@types/cli-progress": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.2.tgz", - "integrity": "sha512-Yt/8rEJalfa9ve2SbfQnwFHrc9QF52JIZYHW3FDaTMpkCvnns26ueKiPHDxyJ0CS//IqjMINTx7R5Xa7k7uFHQ==", + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/@types/cli-progress/-/cli-progress-3.11.3.tgz", + "integrity": "sha512-/+C9xAdVtc+g5yHHkGBThgAA8rYpi5B+2ve3wLtybYj0JHEBs57ivR4x/zGfSsplRnV+psE91Nfin1soNKqz5Q==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/fs-extra": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.1.tgz", - "integrity": "sha512-MxObHvNl4A69ofaTRU8DFqvgzzv8s9yRtaPPm5gud9HDNvpB3GPQFvNuTWAI59B9huVGV5jXYJwbCsmBsOGYWA==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.2.tgz", + "integrity": "sha512-c0hrgAOVYr21EX8J0jBMXGLMgJqVf/v6yxi0dLaJboW9aQPh16Id+z6w2Tx1hm+piJOLv8xPfVKZCLfjPw/IMQ==", "dev": true, "dependencies": { "@types/jsonfile": "*", @@ -707,9 +660,9 @@ } }, "node_modules/@types/jsonfile": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.1.tgz", - "integrity": "sha512-GSgiRCVeapDN+3pqA35IkQwasaCh/0YFH5dEF6S88iDvEn901DjOeH3/QPY+XYP1DFzDZPvIvfeEgk+7br5png==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.2.tgz", + "integrity": "sha512-8t92P+oeW4d/CRQfJaSqEwXujrhH4OEeHRjGU3v1Q8mUS8GPF3yiX26sw4svv6faL2HfBtGTe2xWIoVgN3dy9w==", "dev": true, "dependencies": { "@types/node": "*" @@ -725,33 +678,33 @@ } }, "node_modules/@types/node": { - "version": "18.17.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.2.tgz", - "integrity": "sha512-wBo3KqP/PBqje5TI9UTiuL3yWfP6sdPtjtygSOqcYZWT232dfDeDOnkDps5wqZBP9NgGgYrNejinl0faAuE+HQ==", + "version": "18.18.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.3.tgz", + "integrity": "sha512-0OVfGupTl3NBFr8+iXpfZ8NR7jfFO+P1Q+IO/q0wbo02wYkP5gy36phojeYWpLQ6WAMjl+VfmqUk2YbUfp0irA==", "dev": true }, "node_modules/@types/tar-stream": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.2.tgz", - "integrity": "sha512-1AX+Yt3icFuU6kxwmPakaiGrJUwG44MpuiqPg4dSolRFk6jmvs4b3IbUol9wKDLIgU76gevn3EwE8y/DkSJCZQ==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@types/tar-stream/-/tar-stream-2.2.3.tgz", + "integrity": "sha512-if3mugZfjVkXOMZdFjIHySxY13r6GXPpyOlsDmLffvyI7tLz9wXE8BFjNivXsvUeyJ1KNlOpfLnag+ISmxgxPw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/through": { - "version": "0.0.30", - "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.30.tgz", - "integrity": "sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg==", + "version": "0.0.31", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.31.tgz", + "integrity": "sha512-LpKpmb7FGevYgXnBXYs6HWnmiFyVG07Pt1cnbgM1IhEacITTiUaBXXvOR3Y50ksaJWGSfhbEvQFivQEFGCC55w==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/triple-beam": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", - "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.3.tgz", + "integrity": "sha512-6tOUG+nVHn0cJbVp25JFayS5UE6+xlbcNF9Lo9mU7U0zk3zeUShZied4YEQZjy1JBF043FSkdXw8YkUJuVtB5g==" }, "node_modules/acorn": { "version": "8.10.0", @@ -823,6 +776,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -909,9 +873,9 @@ } }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", + "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -979,17 +943,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/boxen/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1114,18 +1067,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/clean-stack/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -1161,9 +1102,9 @@ } }, "node_modules/cli-spinners": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.0.tgz", - "integrity": "sha512-4/aL9X3Wh0yiMQlE+eeRhWP6vclO3QRtw1JHKIT0FFUs5FjpFmESqtMvYZ0+lbzBw900b95mS0hohy+qn2VK/g==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.1.tgz", + "integrity": "sha512-jHgecW0pxkonBJdrKsqxgRX9AcG+u/5k0Q7WPDfi8AogLAdwxEkyYYNWwZ5GvVFoFx2uiY1eNcSK00fh+1+FyQ==", "engines": { "node": ">=6" }, @@ -1331,12 +1272,6 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1421,6 +1356,14 @@ "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, + "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==", + "engines": { + "node": ">=12" + } + }, "node_modules/dotenv-vault": { "version": "1.25.0", "resolved": "https://registry.npmjs.org/dotenv-vault/-/dotenv-vault-1.25.0.tgz", @@ -1504,12 +1447,6 @@ "is-arrayish": "^0.2.1" } }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true - }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -1520,11 +1457,15 @@ } }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/esprima": { @@ -1568,9 +1509,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-fifo": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.0.tgz", - "integrity": "sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" }, "node_modules/fast-glob": { "version": "3.3.1", @@ -1634,6 +1575,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/filelist": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", @@ -1685,15 +1634,24 @@ "node": ">=8" } }, + "node_modules/find-package": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/find-package/-/find-package-1.0.0.tgz", + "integrity": "sha512-yVn71XCCaNgxz58ERTl8nA/8YYtIQDY9mHSrgFBfiFtdNNfY0h183Vh8BRkKxD8x9TUw3ec290uJKhDVxqGZBw==", + "dev": true, + "dependencies": { + "parents": "^1.0.1" + } + }, "node_modules/fn.name": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", @@ -1781,11 +1739,30 @@ "node": ">=14.14" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true + "node_modules/genversion": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/genversion/-/genversion-3.1.1.tgz", + "integrity": "sha512-/H861PMsihhjgX2qqhTN8egM11V04imhA+3JRFY3jjPua2Sy1NqaqqQPjSP8rdM9jZoKpFhVj9g3Fs9XPCjBYQ==", + "dev": true, + "dependencies": { + "commander": "^7.2.0", + "find-package": "^1.0.0" + }, + "bin": { + "genversion": "bin/genversion.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/genversion/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } }, "node_modules/get-caller-file": { "version": "2.0.5", @@ -1849,13 +1826,10 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -1886,9 +1860,9 @@ } }, "node_modules/http-status-codes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.2.0.tgz", - "integrity": "sha512-feERVo9iWxvnejp3SEfm/+oNG517npqL2/PIA8ORjyOZjGC7TwCRQsZylciLS64i6pJ0wRYz3rkXLRwbtFa8Ng==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==" }, "node_modules/https-proxy-agent": { "version": "5.0.1", @@ -2026,9 +2000,10 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true }, "node_modules/is-core-module": { "version": "2.9.0", @@ -2255,6 +2230,14 @@ "npm": ">=1.4.28" } }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -2497,9 +2480,9 @@ "dev": true }, "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/multistream": { "version": "4.1.0", @@ -2546,9 +2529,9 @@ } }, "node_modules/node-abi": { - "version": "3.45.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.45.0.tgz", - "integrity": "sha512-iwXuFrMAcFVi/ZoZiqq8BzAdsLw9kxDfTC0HMyjXfSL/6CSDAGD5UmR7azrAgWV1zKYq7dUUMj4owusBWKLsiQ==", + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.47.0.tgz", + "integrity": "sha512-2s6B2CWZM//kPgwnuI0KrYwNjfdByE25zvAaEpq9IH4zcNsarH8Ihu/UuX6XMPEogDAxkuUFeZn60pXNHAqn3A==", "dev": true, "dependencies": { "semver": "^7.3.5" @@ -2557,25 +2540,10 @@ "node": ">=10" } }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "dev": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -2671,6 +2639,15 @@ "node": ">=8" } }, + "node_modules/parents": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", + "integrity": "sha512-mXKF3xkoUt5td2DoxpLmtOmZvko9VfFpwRwkKDHSNvgmpLAeBo18YDhcPbBzJq+QLCHMbGOfzia2cX4U+0v9Mg==", + "dev": true, + "dependencies": { + "path-platform": "~0.11.15" + } + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -2709,6 +2686,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-platform": { + "version": "0.11.15", + "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", + "integrity": "sha512-Y30dB6rab1A/nfEKsZxmr01nUotHX0c/ZiIAsCTatEe1CmS5Pm5He7fZ195bPT7RdquoaL8lLxFCMQi/bS7IJg==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -2797,21 +2783,6 @@ "node": ">=10" } }, - "node_modules/pkg-fetch/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/pkg/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -2971,12 +2942,12 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", "dev": true, "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2988,9 +2959,9 @@ } }, "node_modules/resolve/node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -3093,11 +3064,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/shebang-command": { @@ -3179,6 +3157,11 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/simple-swizzle/node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -3488,9 +3471,9 @@ } }, "node_modules/tslib": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", - "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -3505,9 +3488,9 @@ } }, "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "engines": { "node": ">=10" }, @@ -3516,9 +3499,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -3682,6 +3665,14 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, + "node_modules/yaml": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz", + "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==", + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", diff --git a/NodeApp/package.json b/NodeApp/package.json index 8f1ea6b3241f7a2cf8854af300f796178bce933c..b4f8edfe6b9748cec7c7440185ce460496da54cd 100644 --- a/NodeApp/package.json +++ b/NodeApp/package.json @@ -1,6 +1,9 @@ { "name" : "dojo_cli", - "version" : "2.1.0", + "description" : "CLI of the Dojo project", + "version" : "2.2.0", + "license" : "AGPLv3", + "author" : "MichaΓ«l Minelli <dojo@minelli.me>", "main" : "dist/app.js", "bin" : { "dojo": "./dist/app.js" @@ -22,9 +25,11 @@ ] }, "scripts" : { - "build" : "npx tsc", - "start:dev": "npx ts-node src/app.ts", - "test" : "echo \"Error: no test specified\" && exit 1" + "dotenv:build": "npx dotenv-vault local build", + "genversion" : "npx genversion -s -e src/config/Version.ts", + "build" : "npm run genversion; npx tsc", + "start:dev" : "npm run genversion; npx ts-node src/app.ts", + "test" : "echo \"Error: no test specified\" && exit 1" }, "dependencies" : { "ajv" : "^8.12.0", @@ -34,6 +39,7 @@ "chalk" : "^4.1.2", "commander" : "^11.0.0", "dotenv" : "^16.3.1", + "dotenv-expand" : "^10.0.0", "fs-extra" : "^11.1.1", "http-status-codes": "^2.2.0", "inquirer" : "^8.2.5", @@ -41,7 +47,8 @@ "jsonwebtoken" : "^8.5.1", "ora" : "^5.4.1", "tar-stream" : "^3.1.6", - "winston" : "^3.10.0" + "winston" : "^3.10.0", + "yaml" : "^2.3.2" }, "devDependencies": { "@types/fs-extra" : "^11.0.1", @@ -50,6 +57,7 @@ "@types/node" : "^18.17.2", "@types/tar-stream" : "^2.2.2", "dotenv-vault" : "^1.25.0", + "genversion" : "^3.1.1", "pkg" : "^5.8.1", "tiny-typed-emitter" : "^2.1.0", "ts-node" : "^10.9.1", diff --git a/NodeApp/src/app.ts b/NodeApp/src/app.ts index ea7d4ef9a1c6eb8f15517b3df0a0bb2cf56a6dbd..634f276293e465b7a1e2fd599d502d6aeb371586 100644 --- a/NodeApp/src/app.ts +++ b/NodeApp/src/app.ts @@ -1,10 +1,11 @@ // Read from the .env file // ATTENTION : This lines MUST be the first of this file (except for the path import) const path = require('node:path'); -require('dotenv').config({ - path : path.join(__dirname, '../.env'), - DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development' - }); +const myEnv = require('dotenv').config({ + path : path.join(__dirname, '../.env'), + DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development' + }); +require('dotenv-expand').expand(myEnv); require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file diff --git a/NodeApp/src/commander/assignment/AssignmentCommand.ts b/NodeApp/src/commander/assignment/AssignmentCommand.ts index c629ddc14f3ed6785ac5a4b5c7a9fe05b82ef23b..d78a64aa54d0920f49377a029e7de78806b338d1 100644 --- a/NodeApp/src/commander/assignment/AssignmentCommand.ts +++ b/NodeApp/src/commander/assignment/AssignmentCommand.ts @@ -1,7 +1,8 @@ import CommanderCommand from '../CommanderCommand'; -import AssignmentCreateCommand from './AssignmentCreateCommand'; -import AssignmentPublishCommand from './AssignmentPublishCommand'; -import AssignmentUnpublishCommand from './AssignmentUnpublishCommand'; +import AssignmentCreateCommand from './subcommands/AssignmentCreateCommand'; +import AssignmentPublishCommand from './subcommands/AssignmentPublishCommand'; +import AssignmentUnpublishCommand from './subcommands/AssignmentUnpublishCommand'; +import AssignmentCheckCommand from './subcommands/AssignmentCheckCommand'; class AssignmentCommand extends CommanderCommand { @@ -14,6 +15,7 @@ class AssignmentCommand extends CommanderCommand { protected defineSubCommands() { AssignmentCreateCommand.registerOnCommand(this.command); + AssignmentCheckCommand.registerOnCommand(this.command); AssignmentPublishCommand.registerOnCommand(this.command); AssignmentUnpublishCommand.registerOnCommand(this.command); } diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..d46efaa66632054313f3bf246b419c280841287e --- /dev/null +++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCheckCommand.ts @@ -0,0 +1,96 @@ +import CommanderCommand from '../../CommanderCommand'; +import Config from '../../../config/Config'; +import ora from 'ora'; +import util from 'util'; +import { exec } from 'child_process'; +import chalk from 'chalk'; +import AssignmentValidator from '../../../sharedByClients/helpers/Dojo/AssignmentValidator'; +import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper'; + + +const execAsync = util.promisify(exec); + + +class AssignmentCheckCommand extends CommanderCommand { + protected commandName: string = 'check'; + + protected defineCommand() { + this.command + .description('locally run a check of an assignment') + .option('-p, --path <value>', 'assignment path', Config.folders.defaultLocalExercise) + .option('-v, --verbose', 'verbose mode (display docker compose logs in live)') + .action(this.commandAction.bind(this)); + } + + protected async commandAction(options: { path: string, verbose: boolean }): Promise<void> { + const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise; + + const assignmentValidator = new AssignmentValidator(localExercisePath); + + try { + await new Promise<void>((resolve, reject) => { + let spinner: ora.Ora; + + if ( options.verbose ) { + assignmentValidator.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { + if ( displayable ) { + console.log(log); + } + }); + } + + assignmentValidator.events.on('step', (name: string, message: string) => { + console.log(chalk.cyan(message)); + }); + + assignmentValidator.events.on('subStep', (name: string, message: string) => { + spinner = ora({ + text : message, + indent: 4 + }).start(); + + if ( options.verbose && name == 'COMPOSE_RUN' ) { + spinner.info(); + } + }); + + assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => { + if ( error ) { + if ( options.verbose && stepName == 'COMPOSE_RUN' ) { + ora({ + text : message, + indent: 4 + }).start().fail(); + } else { + spinner.fail(message); + } + } else { + if ( options.verbose && stepName == 'COMPOSE_RUN' ) { + ora({ + text : message, + indent: 4 + }).start().succeed(); + } else { + spinner.succeed(message); + } + } + }); + + assignmentValidator.events.on('finished', (success: boolean, exitCode: number) => { + success ? resolve() : reject(); + }); + + assignmentValidator.run(true); + }); + } catch ( error ) { } + + ClientsSharedAssignmentHelper.displayExecutionResults(assignmentValidator, `The assignment is ready to be pushed.`, { + INFO : chalk.bold, + SUCCESS: chalk.green, + FAILURE: chalk.red + }); + } +} + + +export default new AssignmentCheckCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/assignment/AssignmentCreateCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts similarity index 85% rename from NodeApp/src/commander/assignment/AssignmentCreateCommand.ts rename to NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts index c2b384afaf2f8c41e52c0cf836d537912df28ed1..0b28a1c7ff9302c5b0416043e8bc3421cbbfb96b 100644 --- a/NodeApp/src/commander/assignment/AssignmentCreateCommand.ts +++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts @@ -1,12 +1,12 @@ -import CommanderCommand from '../CommanderCommand'; +import CommanderCommand from '../../CommanderCommand'; import chalk from 'chalk'; import ora from 'ora'; -import GitlabManager from '../../managers/GitlabManager'; -import GitlabUser from '../../shared/types/Gitlab/GitlabUser'; -import DojoBackendManager from '../../managers/DojoBackendManager'; -import Toolbox from '../../shared/helpers/Toolbox'; -import AccessesHelper from '../../helpers/AccessesHelper'; -import Assignment from '../../sharedByClients/models/Assignment'; +import GitlabManager from '../../../managers/GitlabManager'; +import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import Toolbox from '../../../shared/helpers/Toolbox'; +import AccessesHelper from '../../../helpers/AccessesHelper'; +import Assignment from '../../../sharedByClients/models/Assignment'; class AssignmentCreateCommand extends CommanderCommand { @@ -68,8 +68,7 @@ class AssignmentCreateCommand extends CommanderCommand { const oraInfo = (message: string) => { ora({ - text : message, - indent: 4 + text: message, indent: 4 }).start().info(); }; diff --git a/NodeApp/src/commander/assignment/AssignmentPublishCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts similarity index 100% rename from NodeApp/src/commander/assignment/AssignmentPublishCommand.ts rename to NodeApp/src/commander/assignment/subcommands/AssignmentPublishCommand.ts diff --git a/NodeApp/src/commander/assignment/AssignmentPublishUnpublishCommandBase.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts similarity index 69% rename from NodeApp/src/commander/assignment/AssignmentPublishUnpublishCommandBase.ts rename to NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts index b5df0c9b02e54abd46fbc438e7005e868f17a551..41593c08b1ac4967b10ebdd62d953016af74e907 100644 --- a/NodeApp/src/commander/assignment/AssignmentPublishUnpublishCommandBase.ts +++ b/NodeApp/src/commander/assignment/subcommands/AssignmentPublishUnpublishCommandBase.ts @@ -1,10 +1,11 @@ -import CommanderCommand from '../CommanderCommand'; -import inquirer from 'inquirer'; -import chalk from 'chalk'; -import SessionManager from '../../managers/SessionManager'; -import ora from 'ora'; -import DojoBackendManager from '../../managers/DojoBackendManager'; -import Assignment from '../../sharedByClients/models/Assignment'; +import CommanderCommand from '../../CommanderCommand'; +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import SessionManager from '../../../managers/SessionManager'; +import ora from 'ora'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import Assignment from '../../../sharedByClients/models/Assignment'; +import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { @@ -56,15 +57,28 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { } assignmentGetSpinner.succeed(`The assignment exists`); + const assignmentCheckAccessSpinner: ora.Ora = ora({ text : 'Checking accesses', indent: 8 }).start(); - if ( !assignment.staff ) { + if ( !assignment.staff.some(staff => staff.gitlabId === SessionManager.profile?.gitlabId) ) { assignmentCheckAccessSpinner.fail(`You are not in the staff of this assignment`); return; } assignmentCheckAccessSpinner.succeed(`You are in the staff of this assignment`); + + + const assignmentIsPublishable: ora.Ora = ora({ + text : 'Checking if the assignment is publishable', + indent: 8 + }).start(); + const isPublishable = await SharedAssignmentHelper.isPublishable(assignment.gitlabId); + if ( !isPublishable.isPublishable ) { + assignmentIsPublishable.fail(`The assignment is not publishable: ${ isPublishable.status?.message }`); + return; + } + assignmentIsPublishable.succeed(`The assignment is publishable`); } { diff --git a/NodeApp/src/commander/assignment/AssignmentUnpublishCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts similarity index 100% rename from NodeApp/src/commander/assignment/AssignmentUnpublishCommand.ts rename to NodeApp/src/commander/assignment/subcommands/AssignmentUnpublishCommand.ts diff --git a/NodeApp/src/commander/exercise/ExerciseCommand.ts b/NodeApp/src/commander/exercise/ExerciseCommand.ts index ef2c3f263f3bf1bdd287016d4d194b41ab44888d..bc122a864f7cc7fd9b32c00879b4427cdaae67e6 100644 --- a/NodeApp/src/commander/exercise/ExerciseCommand.ts +++ b/NodeApp/src/commander/exercise/ExerciseCommand.ts @@ -1,6 +1,6 @@ import CommanderCommand from '../CommanderCommand'; -import ExerciseCreateCommand from './ExerciseCreateCommand'; -import ExerciseRunCommand from './ExerciseRunCommand'; +import ExerciseCreateCommand from './subcommands/ExerciseCreateCommand'; +import ExerciseRunCommand from './subcommands/ExerciseRunCommand'; class ExerciseCommand extends CommanderCommand { diff --git a/NodeApp/src/commander/exercise/ExerciseCreateCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts similarity index 79% rename from NodeApp/src/commander/exercise/ExerciseCreateCommand.ts rename to NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts index 9116ff3a4c5cf8356ab05f57fd171b6eae15702b..bb51c2e6ece83361bd4fa5bd011b8d2e5731f97b 100644 --- a/NodeApp/src/commander/exercise/ExerciseCreateCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts @@ -1,12 +1,12 @@ -import CommanderCommand from '../CommanderCommand'; +import CommanderCommand from '../../CommanderCommand'; import chalk from 'chalk'; -import GitlabManager from '../../managers/GitlabManager'; -import GitlabUser from '../../shared/types/Gitlab/GitlabUser'; +import GitlabManager from '../../../managers/GitlabManager'; +import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; import ora from 'ora'; -import DojoBackendManager from '../../managers/DojoBackendManager'; -import AccessesHelper from '../../helpers/AccessesHelper'; -import Assignment from '../../sharedByClients/models/Assignment'; -import Exercise from '../../sharedByClients/models/Exercise'; +import DojoBackendManager from '../../../managers/DojoBackendManager'; +import AccessesHelper from '../../../helpers/AccessesHelper'; +import Assignment from '../../../sharedByClients/models/Assignment'; +import Exercise from '../../../sharedByClients/models/Exercise'; class ExerciseCreateCommand extends CommanderCommand { @@ -40,8 +40,7 @@ class ExerciseCreateCommand extends CommanderCommand { ora('Checking assignment:').start().info(); const assignmentGetSpinner: ora.Ora = ora({ - text : 'Checking if assignment exists', - indent: 4 + text: 'Checking if assignment exists', indent: 4 }).start(); assignment = await DojoBackendManager.getAssignment(options.assignment); if ( !assignment ) { @@ -51,8 +50,7 @@ class ExerciseCreateCommand extends CommanderCommand { assignmentGetSpinner.succeed(`Assignment "${ options.assignment }" exists`); const assignmentPublishedSpinner: ora.Ora = ora({ - text : 'Checking if assignment is published', - indent: 4 + text: 'Checking if assignment is published', indent: 4 }).start(); if ( !assignment.published ) { assignmentPublishedSpinner.fail(`Assignment "${ assignment.name }" isn't published`); @@ -70,8 +68,7 @@ class ExerciseCreateCommand extends CommanderCommand { const oraInfo = (message: string) => { ora({ - text : message, - indent: 4 + text: message, indent: 4 }).start().info(); }; diff --git a/NodeApp/src/commander/exercise/ExerciseRunCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts similarity index 56% rename from NodeApp/src/commander/exercise/ExerciseRunCommand.ts rename to NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts index 9eb4d8da5a2ec909018a15e052142adc6bcacf09..bac1eb1db39cf5f36fe539855803420d0f872f52 100644 --- a/NodeApp/src/commander/exercise/ExerciseRunCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseRunCommand.ts @@ -1,19 +1,19 @@ -import CommanderCommand from '../CommanderCommand'; -import Config from '../../config/Config'; -import fs from 'node:fs'; -import ora from 'ora'; -import util from 'util'; -import { exec } from 'child_process'; -import chalk from 'chalk'; -import * as os from 'os'; -import path from 'path'; -import ClientsSharedConfig from '../../sharedByClients/config/ClientsSharedConfig'; -import AssignmentFile from '../../shared/types/Dojo/AssignmentFile'; -import ExerciseDockerCompose from '../../sharedByClients/helpers/Dojo/ExerciseDockerCompose'; -import ExerciseResultsValidation from '../../sharedByClients/helpers/Dojo/ExerciseResultsValidation'; -import SharedAssignmentHelper from '../../shared/helpers/Dojo/SharedAssignmentHelper'; -import ExerciseCheckerError from '../../shared/types/Dojo/ExerciseCheckerError'; -import ClientsSharedExerciseHelper from '../../sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper'; +import CommanderCommand from '../../CommanderCommand'; +import Config from '../../../config/Config'; +import fs from 'node:fs'; +import ora from 'ora'; +import util from 'util'; +import { exec } from 'child_process'; +import chalk from 'chalk'; +import * as os from 'os'; +import path from 'path'; +import ClientsSharedConfig from '../../../sharedByClients/config/ClientsSharedConfig'; +import AssignmentFile from '../../../shared/types/Dojo/AssignmentFile'; +import ExerciseDockerCompose from '../../../sharedByClients/helpers/Dojo/ExerciseDockerCompose'; +import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; +import ExerciseCheckerError from '../../../shared/types/Dojo/ExerciseCheckerError'; +import ClientsSharedExerciseHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedExerciseHelper'; +import ExerciseResultsSanitizerAndValidator from '../../../sharedByClients/helpers/Dojo/ExerciseResultsSanitizerAndValidator'; const execAsync = util.promisify(exec); @@ -36,24 +36,26 @@ class ExerciseRunCommand extends CommanderCommand { this.command .description('locally run an exercise') .option('-p, --path <value>', 'exercise path', Config.folders.defaultLocalExercise) + .option('-v, --verbose', 'verbose mode (display docker compose logs in live)') .action(this.commandAction.bind(this)); } private displayExecutionLogs() { ora({ - text : `${ chalk.magenta('Execution logs folder:') } ${ this.folderResultsVolume }`, - indent: 0 + text: `${ chalk.magenta('Execution logs folder:') } ${ this.folderResultsVolume }`, indent: 0 }).start().info(); } - protected async commandAction(options: any): Promise<void> { - const localExercisePath = options.path ?? Config.folders.defaultLocalExercise; + protected async commandAction(options: { path: string, verbose: boolean }): Promise<void> { + const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise; let assignmentFile: AssignmentFile; let exerciseDockerCompose: ExerciseDockerCompose; - let exerciseResultsValidation: ExerciseResultsValidation; + let exerciseResultsValidation: ExerciseResultsSanitizerAndValidator; - // Step 1: Check requirements (if it's an exercise folder and if Docker deamon is running) + let haveResultsVolume: boolean; + + // Step 1: Check requirements (if it's an exercise folder and if Docker daemon is running) { console.log(chalk.cyan('Please wait while we are checking and creating dependencies...')); @@ -64,15 +66,13 @@ class ExerciseRunCommand extends CommanderCommand { ora({ - text : `Checking exercise content:`, - indent: 4 + text: `Checking exercise content:`, indent: 4 }).start().info(); // Exercise folder { const spinner: ora.Ora = ora({ - text : `Checking exercise folder`, - indent: 8 + text: `Checking exercise folder`, indent: 8 }).start(); const files = fs.readdirSync(options.path); @@ -89,36 +89,36 @@ class ExerciseRunCommand extends CommanderCommand { // dojo_assignment.json validity { const spinner: ora.Ora = ora({ - text : `Checking ${ Config.assignment.filename } file`, - indent: 8 + text: `Checking ${ ClientsSharedConfig.assignment.filename } file`, indent: 8 }).start(); - const validationResults = SharedAssignmentHelper.validateDescriptionFile(`${ options.path }/${ Config.assignment.filename }`); + const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(options.path, ClientsSharedConfig.assignment.filename)); if ( !validationResults.isValid ) { - spinner.fail(`The ${ Config.assignment.filename } file is invalid: ${ JSON.stringify(validationResults.errors) }`); + spinner.fail(`The ${ ClientsSharedConfig.assignment.filename } file is invalid: ${ JSON.stringify(validationResults.errors) }`); return; } else { assignmentFile = validationResults.results!; } - spinner.succeed(`The ${ Config.assignment.filename } file is valid`); + haveResultsVolume = assignmentFile.result.volume !== undefined; + + spinner.succeed(`The ${ ClientsSharedConfig.assignment.filename } file is valid`); } - // Docker deamon + // Docker daemon { const spinner: ora.Ora = ora({ - text : `Checking Docker deamon`, - indent: 4 + text: `Checking Docker daemon`, indent: 4 }).start(); try { await execAsync(`cd "${ Config.folders.defaultLocalExercise }";docker ps`); } catch ( error ) { - spinner.fail(`The Docker deamon is not running`); + spinner.fail(`The Docker daemon is not running`); return; } - spinner.succeed(`The Docker deamon is running`); + spinner.succeed(`The Docker daemon is running`); } } @@ -127,29 +127,56 @@ class ExerciseRunCommand extends CommanderCommand { { console.log(chalk.cyan('Please wait while we are running the exercise...')); + let composeFileOverride: string[] = []; const composeOverridePath: string = path.join(localExercisePath, 'docker-compose-override.yml'); + if ( haveResultsVolume ) { + const composeOverride = fs.readFileSync(path.join(__dirname, '../../../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', assignmentFile.result.volume!).replace('{{MOUNT_PATH}}', this.folderResultsExercise); + fs.writeFileSync(composeOverridePath, composeOverride); - const composeOverride = fs.readFileSync(path.join(__dirname, '../../../assets/docker-compose-override.yml'), 'utf8').replace('{{VOLUME_NAME}}', assignmentFile.result.volume).replace('{{MOUNT_PATH}}', this.folderResultsExercise); - fs.writeFileSync(composeOverridePath, composeOverride); + composeFileOverride = [ composeOverridePath ]; + } - exerciseDockerCompose = new ExerciseDockerCompose(this.projectName, assignmentFile, localExercisePath, [ composeOverridePath ]); + exerciseDockerCompose = new ExerciseDockerCompose(this.projectName, assignmentFile, localExercisePath, composeFileOverride); try { await new Promise<void>((resolve, reject) => { let spinner: ora.Ora; + if ( options.verbose ) { + exerciseDockerCompose.events.on('logs', (log: string, _error: boolean, displayable: boolean) => { + if ( displayable ) { + console.log(log); + } + }); + } + exerciseDockerCompose.events.on('step', (name: string, message: string) => { spinner = ora({ - text : message, - indent: 4 + text: message, indent: 4 }).start(); + + if ( options.verbose && name == 'COMPOSE_RUN' ) { + spinner.info(); + } }); exerciseDockerCompose.events.on('endStep', (stepName: string, message: string, error: boolean) => { if ( error ) { - spinner.fail(message); + if ( options.verbose && stepName == 'COMPOSE_RUN' ) { + ora({ + text: message, indent: 4 + }).start().fail(); + } else { + spinner.fail(message); + } } else { - spinner.succeed(message); + if ( options.verbose && stepName == 'COMPOSE_RUN' ) { + ora({ + text: message, indent: 4 + }).start().succeed(); + } else { + spinner.succeed(message); + } } }); @@ -161,7 +188,7 @@ class ExerciseRunCommand extends CommanderCommand { }); } catch ( error ) { } - fs.rmSync(composeOverridePath); + fs.rmSync(composeOverridePath, { force: true }); fs.writeFileSync(this.fileComposeLogs, exerciseDockerCompose.allLogs); if ( !exerciseDockerCompose.success ) { @@ -175,7 +202,7 @@ class ExerciseRunCommand extends CommanderCommand { { console.log(chalk.cyan('Please wait while we are checking the results...')); - exerciseResultsValidation = new ExerciseResultsValidation(this.folderResultsDojo, this.folderResultsExercise); + exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, exerciseDockerCompose.exitCode); try { await new Promise<void>((resolve, reject) => { @@ -183,8 +210,7 @@ class ExerciseRunCommand extends CommanderCommand { exerciseResultsValidation.events.on('step', (name: string, message: string) => { spinner = ora({ - text : message, - indent: 4 + text: message, indent: 4 }).start(); }); @@ -216,9 +242,7 @@ class ExerciseRunCommand extends CommanderCommand { // Step 4: Display results + Volume location { ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults!, exerciseDockerCompose.exitCode, { - INFO : chalk.bold, - SUCCESS: chalk.green, - FAILURE: chalk.red + INFO: chalk.bold, SUCCESS: chalk.green, FAILURE: chalk.red }, `\n\n${ chalk.bold('Execution results folder') } : ${ this.folderResultsVolume }`); } } diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts b/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts deleted file mode 100644 index 33ffe4713dbce6bf01101a2314a03dc8d8d6edae..0000000000000000000000000000000000000000 --- a/NodeApp/src/commander/session/Gitlab/SessionGitlabLoginCommand.ts +++ /dev/null @@ -1,26 +0,0 @@ -import chalk from 'chalk'; -import CommanderCommand from '../../CommanderCommand'; -import GitlabManager from '../../../managers/GitlabManager'; - - -class SessionGitlabLoginCommand extends CommanderCommand { - protected commandName: string = 'login'; - - protected defineCommand() { - this.command - .description('register the gitlab token') - .argument('<token>', 'personal access token from GitLab with api scope') - .action(this.commandAction.bind(this)); - } - - protected async commandAction(token: string): Promise<void> { - console.log(chalk.cyan('Please wait while we are testing your Gitlab token...')); - - GitlabManager.login(token); - - await GitlabManager.testToken(); - } -} - - -export default new SessionGitlabLoginCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/session/SessionCommand.ts b/NodeApp/src/commander/session/SessionCommand.ts index c7089d6b0441979a93133f0cab4a30ee3bad306e..8ec16854a4bc15f6cdfb8534f57c28c2bef128c8 100644 --- a/NodeApp/src/commander/session/SessionCommand.ts +++ b/NodeApp/src/commander/session/SessionCommand.ts @@ -1,7 +1,7 @@ import CommanderCommand from '../CommanderCommand'; -import SessionTestCommand from './SessionTestCommand'; -import SessionAppCommand from './App/SessionAppCommand'; -import SessionGitlabCommand from './Gitlab/SessionGitlabCommand'; +import SessionTestCommand from './subcommands/SessionTestCommand'; +import SessionAppCommand from './subcommands/SessionAppCommand'; +import SessionGitlabCommand from './subcommands/SessionGitlabCommand'; class SessionCommand extends CommanderCommand { diff --git a/NodeApp/src/commander/session/App/SessionAppCommand.ts b/NodeApp/src/commander/session/subcommands/SessionAppCommand.ts similarity index 78% rename from NodeApp/src/commander/session/App/SessionAppCommand.ts rename to NodeApp/src/commander/session/subcommands/SessionAppCommand.ts index d0c943b10a8f3a38fe93fd245c0c533582bffd8e..f8d179efead33b4652c0a994d8aed4cdc9e28e81 100644 --- a/NodeApp/src/commander/session/App/SessionAppCommand.ts +++ b/NodeApp/src/commander/session/subcommands/SessionAppCommand.ts @@ -1,6 +1,6 @@ import CommanderCommand from '../../CommanderCommand'; -import SessionAppLoginCommand from './SessionAppLoginCommand'; -import SessionAppLogoutCommand from './SessionAppLogoutCommand'; +import SessionAppLoginCommand from './application/SessionAppLoginCommand'; +import SessionAppLogoutCommand from './application/SessionAppLogoutCommand'; class SessionAppCommand extends CommanderCommand { diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts b/NodeApp/src/commander/session/subcommands/SessionGitlabCommand.ts similarity index 78% rename from NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts rename to NodeApp/src/commander/session/subcommands/SessionGitlabCommand.ts index 3662faa1e86d0d7f0d78ac71de71c4c4c02254ca..e80ceb264f1d16f458b1f4cb770dc82dd93da817 100644 --- a/NodeApp/src/commander/session/Gitlab/SessionGitlabCommand.ts +++ b/NodeApp/src/commander/session/subcommands/SessionGitlabCommand.ts @@ -1,6 +1,6 @@ import CommanderCommand from '../../CommanderCommand'; -import SessionGitlabLoginCommand from './SessionGitlabLoginCommand'; -import SessionGitlabLogoutCommand from './SessionGitlabLogoutCommand'; +import SessionGitlabLoginCommand from './gitlab/SessionGitlabLoginCommand'; +import SessionGitlabLogoutCommand from './gitlab/SessionGitlabLogoutCommand'; class SessionGitlabCommand extends CommanderCommand { diff --git a/NodeApp/src/commander/session/SessionTestCommand.ts b/NodeApp/src/commander/session/subcommands/SessionTestCommand.ts similarity index 72% rename from NodeApp/src/commander/session/SessionTestCommand.ts rename to NodeApp/src/commander/session/subcommands/SessionTestCommand.ts index d0ab1b62051f3e7a6555081957851cde1461cc7f..57c1bf00b1ea126c17fddea14714d06effdbea40 100644 --- a/NodeApp/src/commander/session/SessionTestCommand.ts +++ b/NodeApp/src/commander/session/subcommands/SessionTestCommand.ts @@ -1,6 +1,6 @@ -import CommanderCommand from '../CommanderCommand'; -import SessionManager from '../../managers/SessionManager'; -import GitlabManager from '../../managers/GitlabManager'; +import CommanderCommand from '../../CommanderCommand'; +import SessionManager from '../../../managers/SessionManager'; +import GitlabManager from '../../../managers/GitlabManager'; class SessionTestCommand extends CommanderCommand { diff --git a/NodeApp/src/commander/session/App/SessionAppLoginCommand.ts b/NodeApp/src/commander/session/subcommands/application/SessionAppLoginCommand.ts similarity index 66% rename from NodeApp/src/commander/session/App/SessionAppLoginCommand.ts rename to NodeApp/src/commander/session/subcommands/application/SessionAppLoginCommand.ts index 514353fd4f34e498125eee4e9427ba5c9cef3790..88deb2e8b9a40cdc8d399393a81257ff327e4992 100644 --- a/NodeApp/src/commander/session/App/SessionAppLoginCommand.ts +++ b/NodeApp/src/commander/session/subcommands/application/SessionAppLoginCommand.ts @@ -1,7 +1,7 @@ import chalk from 'chalk'; -import CommanderCommand from '../../CommanderCommand'; +import CommanderCommand from '../../../CommanderCommand'; import inquirer from 'inquirer'; -import SessionManager from '../../../managers/SessionManager'; +import SessionManager from '../../../../managers/SessionManager'; class SessionAppLoginCommand extends CommanderCommand { @@ -15,13 +15,10 @@ class SessionAppLoginCommand extends CommanderCommand { .action(this.commandAction.bind(this)); } - protected async commandAction(options: any): Promise<void> { + protected async commandAction(options: { user: string, password: string }): Promise<void> { if ( !options.password ) { options.password = (await inquirer.prompt({ - type : 'password', - name : 'password', - message: 'Please enter your password', - mask : '' + type: 'password', name: 'password', message: 'Please enter your password', mask: '' })).password; } diff --git a/NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts b/NodeApp/src/commander/session/subcommands/application/SessionAppLogoutCommand.ts similarity index 72% rename from NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts rename to NodeApp/src/commander/session/subcommands/application/SessionAppLogoutCommand.ts index 650440d8396647ecfc4d634b4eda32347c2473b6..7d051a31a7ebf761ef5fd119b0fb1bdd4b8aa6d6 100644 --- a/NodeApp/src/commander/session/App/SessionAppLogoutCommand.ts +++ b/NodeApp/src/commander/session/subcommands/application/SessionAppLogoutCommand.ts @@ -1,6 +1,6 @@ -import CommanderCommand from '../../CommanderCommand'; +import CommanderCommand from '../../../CommanderCommand'; import inquirer from 'inquirer'; -import SessionManager from '../../../managers/SessionManager'; +import SessionManager from '../../../../managers/SessionManager'; import ora from 'ora'; @@ -17,10 +17,7 @@ class SessionAppLogoutCommand extends CommanderCommand { protected async commandAction(options: any): Promise<void> { if ( !options.force ) { const confirm: boolean = (await inquirer.prompt({ - name : 'confirm', - message: 'Are you sure?', - type : 'confirm', - default: false + name: 'confirm', message: 'Are you sure?', type: 'confirm', default: false })).confirm; if ( !confirm ) { diff --git a/NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLoginCommand.ts b/NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLoginCommand.ts new file mode 100644 index 0000000000000000000000000000000000000000..fae91b6cf934f21e1c40a64f878c9a526b6f8241 --- /dev/null +++ b/NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLoginCommand.ts @@ -0,0 +1,33 @@ +import chalk from 'chalk'; +import CommanderCommand from '../../../CommanderCommand'; +import GitlabManager from '../../../../managers/GitlabManager'; +import inquirer from 'inquirer'; + + +class SessionGitlabLoginCommand extends CommanderCommand { + protected commandName: string = 'login'; + + protected defineCommand() { + this.command + .description('register the gitlab token') + .option('-t, --token <string>', 'personal access token from GitLab with api scope') + .action(this.commandAction.bind(this)); + } + + protected async commandAction(options: { token: string }): Promise<void> { + if ( !options.token ) { + options.token = (await inquirer.prompt({ + type: 'password', name: 'token', message: 'Please enter your gitlab token', mask: '' + })).token; + } + + console.log(chalk.cyan('Please wait while we are testing your Gitlab token...')); + + GitlabManager.login(options.token); + + await GitlabManager.testToken(); + } +} + + +export default new SessionGitlabLoginCommand(); \ No newline at end of file diff --git a/NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts b/NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLogoutCommand.ts similarity index 72% rename from NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts rename to NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLogoutCommand.ts index 6b86883c7f4021362d6b0ca785d95cd7101e2a29..4d43d379fbbc185cb571cf1039460e1c620965dc 100644 --- a/NodeApp/src/commander/session/Gitlab/SessionGitlabLogoutCommand.ts +++ b/NodeApp/src/commander/session/subcommands/gitlab/SessionGitlabLogoutCommand.ts @@ -1,7 +1,7 @@ -import CommanderCommand from '../../CommanderCommand'; +import CommanderCommand from '../../../CommanderCommand'; import inquirer from 'inquirer'; import ora from 'ora'; -import GitlabManager from '../../../managers/GitlabManager'; +import GitlabManager from '../../../../managers/GitlabManager'; class SessionGitlabLogoutCommand extends CommanderCommand { @@ -17,10 +17,7 @@ class SessionGitlabLogoutCommand extends CommanderCommand { protected async commandAction(options: any): Promise<void> { if ( !options.force ) { const confirm: boolean = (await inquirer.prompt({ - name : 'confirm', - message: 'Are you sure?', - type : 'confirm', - default: false + name: 'confirm', message: 'Are you sure?', type: 'confirm', default: false })).confirm; if ( !confirm ) { diff --git a/NodeApp/src/config/Config.ts b/NodeApp/src/config/Config.ts index dca31f4290df75bdce9722cc600bdcbc640a1078..d1935ab6335da38804364d3065639f65f1629882 100644 --- a/NodeApp/src/config/Config.ts +++ b/NodeApp/src/config/Config.ts @@ -6,12 +6,12 @@ class Config { folder: string; file: string; }; - public readonly folders: { - defaultLocalExercise: string + public readonly gitlab: { + cliReleasePage: string }; - public assignment: { - filename: string + public readonly folders: { + defaultLocalExercise: string }; public readonly exercise: { @@ -24,12 +24,12 @@ class Config { file : process.env.LOCAL_CONFIG_FILE || '' }; - this.folders = { - defaultLocalExercise: process.env.LOCAL_EXERCISE_DEFAULT_FOLDER || './' + this.gitlab = { + cliReleasePage: process.env.GITLAB_CLI_RELEASE_PAGE || '' }; - this.assignment = { - filename: process.env.ASSIGNMENT_FILENAME || '' + this.folders = { + defaultLocalExercise: process.env.LOCAL_EXERCISE_DEFAULT_FOLDER || './' }; this.exercise = { diff --git a/NodeApp/src/config/LocalConfig.ts b/NodeApp/src/config/LocalConfig.ts index 0e5f4e7eb0ad75a7c3e49797bd50a11da6554693..656c0c6bc47dc684556b816178548989086c9313 100644 --- a/NodeApp/src/config/LocalConfig.ts +++ b/NodeApp/src/config/LocalConfig.ts @@ -1,10 +1,10 @@ -import * as fs from 'fs'; -import logger from '../shared/logging/WinstonLogger'; -import SessionManager from '../managers/SessionManager'; -import Config from './Config'; -import LocalConfigKeys from '../types/LocalConfigKeys'; -import GitlabManager from '../managers/GitlabManager'; -import JSON5 from 'json5'; +import * as fs from 'fs'; +import SessionManager from '../managers/SessionManager'; +import Config from './Config'; +import LocalConfigKeys from '../types/LocalConfigKeys'; +import GitlabManager from '../managers/GitlabManager'; +import JSON5 from 'json5'; +import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; class LocalConfig { @@ -24,23 +24,43 @@ class LocalConfig { try { this._config = JSON5.parse(fs.readFileSync(this.configPath).toString()); - SessionManager.token = this._config.apiToken; - GitlabManager.token = this._config.gitlabPersonalToken; - } catch ( error ) { } + if ( LocalConfigKeys.API_TOKEN_ENV in this._config && ClientsSharedConfig.apiURL in this._config[LocalConfigKeys.API_TOKEN_ENV] ) { + SessionManager.token = this._config[LocalConfigKeys.API_TOKEN_ENV][ClientsSharedConfig.apiURL]; + } else { + SessionManager.token = this._config[LocalConfigKeys.API_TOKEN]; + } + + GitlabManager.token = this._config[LocalConfigKeys.GITLAB_PERSONAL_TOKEN]; + } catch ( error ) { + console.log(error); + } } updateConfig(key: LocalConfigKeys, value: any) { - if ( (this._config as any)[key] === value ) { + let previousValue = (this._config as any)[key]; + if ( key === LocalConfigKeys.API_TOKEN && (!(LocalConfigKeys.API_TOKEN_ENV in this._config) || !(ClientsSharedConfig.apiURL in this._config[LocalConfigKeys.API_TOKEN_ENV])) ) { + previousValue = null; + } + + if ( previousValue === value ) { return; } - (this._config as any)[key] = value; + if ( key === LocalConfigKeys.API_TOKEN ) { + delete (this._config as any)[LocalConfigKeys.API_TOKEN]; + + if ( !(LocalConfigKeys.API_TOKEN_ENV in this._config) ) { + (this._config as any)[LocalConfigKeys.API_TOKEN_ENV] = {}; + } + + (this._config as any)[LocalConfigKeys.API_TOKEN_ENV][ClientsSharedConfig.apiURL] = value; + } else { + (this._config as any)[key] = value; + } try { fs.writeFileSync(this.configPath, JSON5.stringify(this._config, null, 4)); - } catch ( error ) { - logger.error(error); - } + } catch ( error ) { } } } diff --git a/NodeApp/src/managers/GitlabManager.ts b/NodeApp/src/managers/GitlabManager.ts index 89561033e3a1e790f55e61a7a190a9d26db5efc0..92ce994a5027dfbac721c413d1625c44621f32b7 100644 --- a/NodeApp/src/managers/GitlabManager.ts +++ b/NodeApp/src/managers/GitlabManager.ts @@ -1,17 +1,17 @@ -import LocalConfig from '../config/LocalConfig'; -import LocalConfigKeys from '../types/LocalConfigKeys'; -import axios from 'axios'; -import ora from 'ora'; -import GitlabUser from '../shared/types/Gitlab/GitlabUser'; -import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; -import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; +import LocalConfig from '../config/LocalConfig'; +import LocalConfigKeys from '../types/LocalConfigKeys'; +import axios from 'axios'; +import ora from 'ora'; +import GitlabUser from '../shared/types/Gitlab/GitlabUser'; +import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; +import SharedConfig from '../shared/config/SharedConfig'; class GitlabManager { private _token: string | null = null; private getApiUrl(route: GitlabRoute): string { - return `${ ClientsSharedConfig.gitlab.apiURL }${ route }`; + return `${ SharedConfig.gitlab.apiURL }${ route }`; } get isLogged(): boolean { diff --git a/NodeApp/src/managers/HttpManager.ts b/NodeApp/src/managers/HttpManager.ts index 2be4d248355e51f317c718e4657f481a7a0e7cbd..5b8ea67c2a7d802a599ecb304bc04056737219dc 100644 --- a/NodeApp/src/managers/HttpManager.ts +++ b/NodeApp/src/managers/HttpManager.ts @@ -1,14 +1,19 @@ import axios, { AxiosRequestHeaders } from 'axios'; import SessionManager from './SessionManager'; import FormData from 'form-data'; -import logger from '../shared/logging/WinstonLogger'; import GitlabManager from './GitlabManager'; import { StatusCodes } from 'http-status-codes'; import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; +import { version } from '../config/Version'; +import DojoBackendResponse from '../shared/types/Dojo/DojoBackendResponse'; +import DojoStatusCode from '../shared/types/Dojo/DojoStatusCode'; +import boxen from 'boxen'; +import Config from '../config/Config'; +import SharedConfig from '../shared/config/SharedConfig'; class HttpManager { - public handleCommandErrors: boolean = true; + public handleAuthorizationCommandErrors: boolean = true; registerAxiosInterceptor() { this.registerRequestInterceptor(); @@ -32,9 +37,12 @@ class HttpManager { if ( SessionManager.isLogged ) { config.headers.Authorization = `Bearer ${ SessionManager.token }`; } + + config.headers['client'] = 'DojoCLI'; + config.headers['client-version'] = version; } - if ( GitlabManager.isLogged && config.url && config.url.indexOf(ClientsSharedConfig.gitlab.apiURL) !== -1 ) { + if ( GitlabManager.isLogged && config.url && config.url.indexOf(SharedConfig.gitlab.apiURL) !== -1 ) { config.headers['PRIVATE-TOKEN'] = GitlabManager.token; } @@ -42,6 +50,19 @@ class HttpManager { }); } + private requestError(message: string) { + console.log(boxen(message, { + title : 'Request error', + titleAlignment: 'center', + borderColor : 'red', + borderStyle : 'bold', + margin : 1, + padding : 1, + textAlignment : 'left' + })); + process.exit(1); + } + private registerResponseInterceptor() { axios.interceptors.response.use((response) => { if ( response.data && response.data.sessionToken ) { @@ -51,28 +72,39 @@ class HttpManager { return response; }, (error) => { if ( error.response ) { - if ( this.handleCommandErrors ) { + if ( error.response.status === StatusCodes.METHOD_NOT_ALLOWED && error.response.data ) { + const data: DojoBackendResponse<{}> = error.response.data; + + switch ( data.code ) { + case DojoStatusCode.CLIENT_NOT_SUPPORTED: + this.requestError('Client not recognized by the server. Please contact the administrator.'); + break; + case DojoStatusCode.CLIENT_VERSION_NOT_SUPPORTED: + this.requestError(`CLI version not anymore supported by the server. Please update the CLI.\nYou can download the latest stable version (latest release without "-dev" suffix) on this page:\n${ Config.gitlab.cliReleasePage }`); + break; + default: + break; + } + } + + if ( this.handleAuthorizationCommandErrors ) { if ( error.response.url && error.response.url.indexOf(ClientsSharedConfig.apiURL) !== -1 ) { switch ( error.response.status ) { - case StatusCodes.UNAUTHORIZED: // Unauthorized - logger.error('Session expired or inexistent. Please login again.'); - process.exit(1); + case StatusCodes.UNAUTHORIZED: + this.requestError('Session expired or does not exist. Please login again.'); break; - case StatusCodes.FORBIDDEN: // Forbidden - logger.error('Forbidden access.'); - process.exit(1); + case StatusCodes.FORBIDDEN: + this.requestError('Forbidden access.'); break; } } } else { - this.handleCommandErrors = true; + this.handleAuthorizationCommandErrors = true; } } else { - logger.error('Error connecting to the server.'); - process.exit(1); + this.requestError('Error connecting to the server. Please check your internet connection. If the problem persists, please contact the administrator.'); } - return Promise.reject(error); }); } diff --git a/NodeApp/src/managers/SessionManager.ts b/NodeApp/src/managers/SessionManager.ts index 6e492fa8a103e1f0904778931d18e3499782ef8f..28082f59a9345997c53ddb8cb4a04eeab60e43eb 100644 --- a/NodeApp/src/managers/SessionManager.ts +++ b/NodeApp/src/managers/SessionManager.ts @@ -97,7 +97,7 @@ class SessionManager { ora('Checking Dojo session: ').start().info(); } - HttpManager.handleCommandErrors = false; + HttpManager.handleAuthorizationCommandErrors = false; const spinner: ora.Ora = ora({ text : `Testing Dojo session`, diff --git a/NodeApp/src/shared b/NodeApp/src/shared index 8d7e3ca0cca10e874ac48e19e47da8a1491ccba7..efe1bf313f57d1826faf935c183d37a0835f8c2d 160000 --- a/NodeApp/src/shared +++ b/NodeApp/src/shared @@ -1 +1 @@ -Subproject commit 8d7e3ca0cca10e874ac48e19e47da8a1491ccba7 +Subproject commit efe1bf313f57d1826faf935c183d37a0835f8c2d diff --git a/NodeApp/src/sharedByClients b/NodeApp/src/sharedByClients index 4ff3846e9415a6122b0b966be089eec3f0117f4f..d9379b055a4626e4b35cf4cc4a7429040a4aeaf7 160000 --- a/NodeApp/src/sharedByClients +++ b/NodeApp/src/sharedByClients @@ -1 +1 @@ -Subproject commit 4ff3846e9415a6122b0b966be089eec3f0117f4f +Subproject commit d9379b055a4626e4b35cf4cc4a7429040a4aeaf7 diff --git a/NodeApp/src/types/LocalConfigKeys.ts b/NodeApp/src/types/LocalConfigKeys.ts index 7f1e6ab7f5b842f4cb58827e1ef02a5c0da0a3f6..f64751d4b278c48815dbab243a567257cc21ed04 100644 --- a/NodeApp/src/types/LocalConfigKeys.ts +++ b/NodeApp/src/types/LocalConfigKeys.ts @@ -1,5 +1,6 @@ enum LocalConfigKeys { API_TOKEN = 'apiToken', + API_TOKEN_ENV = 'apiTokenEnv', GITLAB_PERSONAL_TOKEN = 'gitlabPersonalToken', } diff --git a/Wiki/Tutorials/1-Assignment-creation.md b/Wiki/Tutorials/1-Assignment-creation.md index 16441292665f46c699b6d6fb419dd9129f430504..848d3b9e3f9be09a460bc3b69e7a55b47ad320d7 100644 --- a/Wiki/Tutorials/1-Assignment-creation.md +++ b/Wiki/Tutorials/1-Assignment-creation.md @@ -118,8 +118,11 @@ services: dockerfile: Dockerfile volumes: - hello_world_volume:/result # <hello_world_volume> must be the same as below but - # the name may be arbitrary. This volume must be - # present in the dojo_assignment.json file under the field + # the name may be arbitrary. The volume is optional but + # you can provide it for show details of the execution like + # tests passed or not or simply any log file you think that + # can be useful for the students. If it's present, this volume + # must be present in the dojo_assignment.json file under the field # "result": { # "volume": "hello_world_volume", # ... @@ -129,18 +132,25 @@ volumes: ``` In this file, we see the definition of a `hello_world_volume` this is an arbitrary name and can be changed but it must be coherent in this file and in the `dojo_assignment.json` file (more on this later configuration file in [The dojo configuration file](#the-dojo-configuration-file)). -This volume is responsible of mounting `/result/` directory which will contain all +This (optional) volume is responsible of mounting `/result/` directory which will contain all the output generated by our assignment (again the name can be changed). In particular -in this director e will be required to create a `dojo_assignment.json` file +in this director he will be required to create a `dojo_assignment.json` file that contains at least if the assignment was successfully performed (more on that at the end of the [Creating the assignment files](#creating-the-assignment-files) section). ## The dojo configuration file The `dojo_assignment.json` file contains the general configuration of the assignment. -The important configuration parameters are: -- the *immutable files* which are files that will be overwritten when the compilation pipeline is run (even if the student -modifies these files the modifications will not be taken into account), -- the *results* which is the volume corresponding to the `docker-compose.yml` file. +The configuration parameters are: +- the `dojoAssignmentVersion` which define the version of the dojo assignment file (actually only version 1 is available), +- the `version` of you assignment, +- the `immutable` field which are files or directories that will be overwritten when the compilation pipeline is run (even if the student +modifies these files the modifications will not be taken into account). Each immutable is defined by: + - `path` **(required)**: the path of the immutable file or directory, + - `description` _(optional)_: provides a description of the immutable for the students or Dojo interface, + - `isDirectory` _(optional)_: `true` if the immutable is a directory, `false` otherwise (default), +- the `results` which provide information to the Dojo for finding results of the execution. The *result* field is defined by: + - `container` **(required)**: the name of the service in the docker compose file that will be run (his dependencies will be run automatically). In our case it is `hello_world`, + - `volume` _(optional)_: this field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file. In its default form `dojo_assignment.json` file contains ```json @@ -160,13 +170,8 @@ In its default form `dojo_assignment.json` file contains } } ``` -Here we see only one immutable file which is the `Dockerfile` (the `isDirectory` field is `false` but it is possible to make -complete directories immutable) and we see that: -* the value of the `container` field (`hello_world`) corresponds to -the value of the `container_name` field in the `docker-compose.yml` file, -* the value of the `volume` field (`hello_world_volume`) corresponds to the `volumes` field in the `docker-compose.yml` file. -This file will be completed in [The immutable files](#the-immutable-files) sectino with the files of our assignment that will be +This file will be completed in [The immutable files](#the-immutable-files) section with the files of our assignment that will be created in the [next section](#creating-the-assignment-files). ## Creating the assignment files @@ -250,21 +255,23 @@ if [ $? -eq 0 ]; then if [ $? -ne 0 ]; then echo "Output is wrong:"; cat result/diff_output.txt + exit(1); else echo "All tests were a complete success" GLOBAL_SUCCESS=true + exit(0); fi else echo "Execution failed"; + exit(2); fi else echo "Compilation failed." + exit(3); fi - -jq --null-input --arg success $GLOBAL_SUCCESS \ - '{"success": $success | test("true")}' > /result/results.json ``` +We use the exit code to say to Dojo if the exercise was a success (exit code `0`) or a failure (all other exit codes). Here one can see the creation of two different files that are located in the `result` directory: - `result/results.json` - `result/diff_output.txt` @@ -273,6 +280,19 @@ The `results.json` is mandatory to be created and must at least contain the assignment is a success or a failure. The other files present in the `result` folder can be retrieved by the students. +In the result directory you can provide the optional `result.json` file that can contain more details about the tests: +``` +successfulTests?: number; +failedTests?: number; + +successfulTestsList?: Array<string>; +failedTestsList?: Array<string>; +``` +- the `successfulTests` which provide the number of successfully passed tests, +- the `failedTests` which provide the number of failed tests, +- the `successfulTestsList` which provide the list (of string) of successfully passed tests, +- the `failedTestsList` which provide the list (of string) of failed tests, + To test if everything works according to plan, one can again use the command ```bash $ docker compose run --build hello_world @@ -375,6 +395,39 @@ Pour ce faire, il faut modifier le fichier `src/function.c` sous la ligne annotΓ©e avec `TODO`. Bonne chance! ``` +## Validate the assignment + +Now that the assignment is ready, we must validate it. +You can do it first locally with the command +```bash +$ dojo assignment check +``` +```console +Please wait while we are checking requirements... + β Docker daemon is running + β All required files exists +Please wait while we are validating dojo_assignment.json file... + β dojo_assignment.json file schema is valid + β Immutable files are valid +Please wait while we are validating docker compose file... + β Docker compose file structure is valid + β Docker compose file content is valid +Please wait while we are validating dockerfiles... + β Docker compose file content is valid +Please wait while we are running the assignment... + β Docker Compose file run successfully + β Linked services logs acquired + β Containers stopped and removed + + ββββββββββββββββββ Results ββββββββββββββββββ + β β + β Global result : β Success β + β β + β The assignment is ready to be pushed. β + β β + βββββββββββββββββββββββββββββββββββββββββββββ +``` + ## Publish the work Now that the assignment is ready, we must publish it. First add/commit/push all the @@ -394,7 +447,9 @@ c_hello_world/ βββ Makefile ``` Then one must *publish* the assignment for the students to be able to perform to get the exercise. +A pipeline will be automatically run to check that the assignment is valid (the same as the previous state). +Wait for the successfully completion of this pipeline and then you are ready to publish the assignment with the command: ```bash $ dojo assignment publish c_hello_world ``` diff --git a/Wiki/UserDocumentation/2-Assignment-creation.md b/Wiki/UserDocumentation/2-Assignment-creation.md index 15c7392aaaa51cc1a762e9c2e0600ecf4577ae1d..76d65404f7d920ce8312ebc5f39483ef5eb71624 100644 --- a/Wiki/UserDocumentation/2-Assignment-creation.md +++ b/Wiki/UserDocumentation/2-Assignment-creation.md @@ -48,7 +48,7 @@ dojo assignment create git clone ssh://git@ssh.hesge.ch:10572/dojo/assignment/unique_name.git ``` 3. Modify the `unique_name` assignment as you want (modify the Dockerfile, docker-compose.yml files, add a readme, source code, compilation tools, etc.). Commit and push our work (soonβ’ more details will be provided on how to create assignment). -4. Once the assignment is done it must be published to be available to students: +4. Once the assignment is done and validated by the pipeline it must be published to be available to students: ```bash dojo assignment publish unique_name ```