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
 ```