diff --git a/CHANGELOG.md b/CHANGELOG.md index a83211896256b0101746b3cd611673213b02a8b5..faa668e6461481db7ee0c4dfe9aee378ecba85bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,20 @@ - No modifications / Keep major and minors versions in sync with all parts of the project --> +## 3.2.0 (???) -## 3.1.0 (???) +### ✨ Feature +- **AssignmentCheck**: Add linter for help improve the quality of the assignment (the linter will not throw errors but warnings) +- **CLI**: Add possibility to clone a repository at creation + +### 🤏 Minor change +- **Exercices**: Set names of students in exercise name in alphabetical order + +### 🐛 Bugfix +- **CLI**: Show a correct error message when the user is not logged into the Dojo server + + +## 3.1.0 (2023-12-06) ### 🔨 Internal / Developers - **Typescript**: Add linter (ESLint) diff --git a/NodeApp/package-lock.json b/NodeApp/package-lock.json index 348f49dc0943f7978b4999de37c5f06c5b0199e7..22d254cfdbd40a28109101927822f7b1d437a9c1 100644 --- a/NodeApp/package-lock.json +++ b/NodeApp/package-lock.json @@ -1,12 +1,12 @@ { "name": "dojo_cli", - "version": "3.1.2", + "version": "3.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dojo_cli", - "version": "3.1.2", + "version": "3.2.0", "license": "AGPLv3", "dependencies": { "ajv": "^8.12.0", diff --git a/NodeApp/package.json b/NodeApp/package.json index 1b72e69833e4ed195b6c76fc52eb8c798cb2f0f7..9090367219b7b21fd7593169c4ba608a473d9258 100644 --- a/NodeApp/package.json +++ b/NodeApp/package.json @@ -1,7 +1,7 @@ { "name" : "dojo_cli", "description" : "CLI of the Dojo project", - "version" : "3.1.2", + "version" : "3.2.0", "license" : "AGPLv3", "author" : "Michaël Minelli <dojo@minelli.me>", "main" : "dist/app.js", diff --git a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts index 623f9b64116a5229814aa481de7087914ed7b82d..3429b7518bb8694b892f2037f1ed138b163b28e9 100644 --- a/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts +++ b/NodeApp/src/commander/assignment/subcommands/AssignmentCreateCommand.ts @@ -1,12 +1,12 @@ import CommanderCommand from '../../CommanderCommand'; import chalk from 'chalk'; import ora from 'ora'; -import GitlabManager from '../../../managers/GitlabManager'; +import AccessesHelper from '../../../helpers/AccessesHelper'; +import Assignment from '../../../sharedByClients/models/Assignment'; import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; +import GitlabManager from '../../../managers/GitlabManager'; 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 { @@ -19,12 +19,14 @@ class AssignmentCreateCommand extends CommanderCommand { .option('-i, --members_id <ids...>', 'list of gitlab members ids (teaching staff) to add to the repository') .option('-u, --members_username <usernames...>', 'list of gitlab members username (teaching staff) to add to the repository') .option('-t, --template <string>', 'id or url of the template (http/s and ssh urls are possible)') + .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)') .action(this.commandAction.bind(this)); } - protected async commandAction(options: { name: string, template?: string, members_id?: Array<number>, members_username?: Array<string> }): Promise<void> { + protected async commandAction(options: { name: string, template?: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }): Promise<void> { let members!: Array<GitlabUser> | false; let templateIdOrNamespace: string | null = null; + let assignment!: Assignment; // Check access and retrieve data { @@ -64,7 +66,7 @@ class AssignmentCreateCommand extends CommanderCommand { console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...')); try { - const assignment: Assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace); + assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace); const oraInfo = (message: string) => { ora({ @@ -81,6 +83,15 @@ class AssignmentCreateCommand extends CommanderCommand { return; } } + + // Clone the repository + { + if ( options.clone ) { + console.log(chalk.cyan('Please wait while we are cloning the repository...')); + + await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0); + } + } } } diff --git a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts index 9660a3e753c67c3e2c590c39cef4adae763a3e9f..c728d5ab785878da794fe70c665874cfc6da81a9 100644 --- a/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts +++ b/NodeApp/src/commander/exercise/subcommands/ExerciseCreateCommand.ts @@ -18,12 +18,14 @@ class ExerciseCreateCommand extends CommanderCommand { .requiredOption('-a, --assignment <value>', 'assignment source (Dojo assignment ID, Dojo assignment name or Gitlab assignment URL)') .option('-i, --members_id <ids...>', 'list of gitlab members ids (group\'s student) to add to the repository') .option('-u, --members_username <usernames...>', 'list of gitlab members username (group\'s student) to add to the repository') + .option('-c, --clone [string]', 'automatically clone the repository (SSH required) in the specified directory (this will create a subdirectory with the assignment name)') .action(this.commandAction.bind(this)); } - protected async commandAction(options: { assignment: string, members_id?: Array<number>, members_username?: Array<string> }): Promise<void> { + protected async commandAction(options: { assignment: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean }): Promise<void> { let members!: Array<GitlabUser> | false; let assignment!: Assignment | undefined; + let exercise!: Exercise; // Check access and retrieve data { @@ -66,7 +68,7 @@ class ExerciseCreateCommand extends CommanderCommand { console.log(chalk.cyan('Please wait while we are creating the exercise (approximately 10 seconds)...')); try { - const exercise: Exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members); + exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members); const oraInfo = (message: string) => { ora({ @@ -84,6 +86,15 @@ class ExerciseCreateCommand extends CommanderCommand { return; } } + + // Clone the repository + { + if ( options.clone ) { + console.log(chalk.cyan('Please wait while we are cloning the repository...')); + + await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, `DojoExercise - ${ exercise.assignmentName }`, true, 0); + } + } } } diff --git a/NodeApp/src/managers/GitlabManager.ts b/NodeApp/src/managers/GitlabManager.ts index 67de5c9dad4cfe101e7c270410bd80448b9e727e..356d9e26d61f1e55a591be9e6c811271c9ef8323 100644 --- a/NodeApp/src/managers/GitlabManager.ts +++ b/NodeApp/src/managers/GitlabManager.ts @@ -4,6 +4,8 @@ import GitlabUser from '../shared/types/Gitlab/GitlabUser'; import GitlabRoute from '../shared/types/Gitlab/GitlabRoute'; import SharedConfig from '../shared/config/SharedConfig'; import GitlabRepository from '../shared/types/Gitlab/GitlabRepository'; +import fs from 'fs-extra'; +import { spawn } from 'child_process'; class GitlabManager { @@ -179,6 +181,44 @@ class GitlabManager { return members; } + + public async cloneRepository(clonePath: string | boolean, repositorySshUrl: string, folderName?: string, verbose: boolean = false, verboseIndent: number = 0) { + let path = './'; + if ( typeof clonePath === 'string' ) { + path = clonePath; + + fs.mkdirSync(path, { recursive: true }); + } + + let cloningSpinner!: ora.Ora; + if ( verbose ) { + cloningSpinner = ora({ + text : 'Cloning the repository...', + indent: verboseIndent + }).start(); + } + + try { + await new Promise<void>((resolve, reject) => { + const gitClone = spawn(`git clone ${ repositorySshUrl } "${ folderName ?? '' }"`, { + cwd : path, + shell: true + }); + + gitClone.on('exit', (code) => { + code !== null && code == 0 ? resolve() : reject(); + }); + }); + + if ( verbose ) { + cloningSpinner.succeed('Repository cloned'); + } + } catch ( error ) { + if ( verbose ) { + cloningSpinner.fail('Error while cloning the repository'); + } + } + } }