Skip to content
Snippets Groups Projects
Commit 54c05d1f authored by Kevin Bonga's avatar Kevin Bonga
Browse files

initial commit

parents
No related branches found
No related tags found
No related merge requests found
Showing
with 984 additions and 0 deletions
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/DojoCLI.iml" filepath="$PROJECT_DIR$/.idea/DojoCLI.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" />
</component>
</project>
\ No newline at end of file
volumes:
{{VOLUME_NAME}}:
driver_opts:
type : none
device: {{MOUNT_PATH}}
o : bind
This diff is collapsed.
{
"name" : "dojo_cli",
"description" : "CLI of the Dojo project",
"version" : "3.0.1",
"license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>",
"main" : "dist/app.js",
"bin" : {
"dojo": "./dist/app.js"
},
"pkg" : {
"scripts": [],
"assets" : [
"node_modules/axios/dist/node/axios.cjs",
".env",
"assets/**/*"
],
"targets": [
"node18-macos-arm64",
"node18-macos-x64",
"node18-linux-arm64",
"node18-linux-x64",
"node18-win-arm64",
"node18-win-x86"
]
},
"scripts" : {
"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",
"appdata-path" : "^1.0.0",
"axios" : "^1.4.0",
"boxen" : "^5.1.2",
"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",
"json5" : "^2.2.3",
"jsonwebtoken" : "^8.5.1",
"openurl" : "^1.1.1",
"ora" : "^5.4.1",
"tar-stream" : "^3.1.6",
"winston" : "^3.10.0",
"yaml" : "^2.3.2"
},
"devDependencies": {
"@types/fs-extra" : "^11.0.1",
"@types/inquirer" : "^8.2.6",
"@types/jsonwebtoken": "^8.5.9",
"@types/node" : "^18.17.2",
"@types/openurl" : "^1.0.1",
"@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",
"typescript" : "^5.1.6"
}
}
// 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');
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
import CommanderApp from './commander/CommanderApp';
import HttpManager from './managers/HttpManager';
HttpManager.registerAxiosInterceptor();
new CommanderApp();
\ No newline at end of file
import { Command } from 'commander';
import SessionCommand from './session/SessionCommand';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import AssignmentCommand from './assignment/AssignmentCommand';
import ExerciseCommand from './exercise/ExerciseCommand';
class CommanderApp {
program: Command = new Command();
constructor() {
this.program
.name('dojo')
.description('CLI of the Dojo application')
.version('{{VERSION}}')
.showHelpAfterError()
.configureHelp({
showGlobalOptions: true,
sortOptions : true,
sortSubcommands : true
})
.option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL);
this.program.on('option:host', () => {
ClientsSharedConfig.apiURL = this.program.opts().host;
});
this.registerCommands();
this.program.parse();
}
private registerCommands() {
SessionCommand.registerOnCommand(this.program);
AssignmentCommand.registerOnCommand(this.program);
ExerciseCommand.registerOnCommand(this.program);
}
}
export default CommanderApp;
\ No newline at end of file
import { Command } from 'commander';
abstract class CommanderCommand {
protected abstract commandName: string;
command: Command = new Command();
registerOnCommand(parent: Command) {
this.command = parent.command(this.commandName);
this.defineCommand();
this.defineSubCommands();
};
protected abstract defineCommand(): void;
protected defineSubCommands() {}
protected abstract commandAction(...args: Array<any>): Promise<void>;
}
export default CommanderCommand;
\ No newline at end of file
import CommanderCommand from '../CommanderCommand';
import AssignmentCreateCommand from './subcommands/AssignmentCreateCommand';
import AssignmentPublishCommand from './subcommands/AssignmentPublishCommand';
import AssignmentUnpublishCommand from './subcommands/AssignmentUnpublishCommand';
import AssignmentCheckCommand from './subcommands/AssignmentCheckCommand';
class AssignmentCommand extends CommanderCommand {
protected commandName: string = 'assignment';
protected defineCommand() {
this.command
.description('manage an assignment');
}
protected defineSubCommands() {
AssignmentCreateCommand.registerOnCommand(this.command);
AssignmentCheckCommand.registerOnCommand(this.command);
AssignmentPublishCommand.registerOnCommand(this.command);
AssignmentUnpublishCommand.registerOnCommand(this.command);
}
protected async commandAction(options: any): Promise<void> { }
}
export default new AssignmentCommand();
\ No newline at end of file
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
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';
class AssignmentCreateCommand extends CommanderCommand {
protected commandName: string = 'create';
protected defineCommand() {
this.command
.description('create a new repository for an assignment')
.requiredOption('-n, --name <name>', 'name of the assignment')
.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)')
.action(this.commandAction.bind(this));
}
protected async commandAction(options: any): Promise<void> {
let members!: Array<GitlabUser> | false;
let templateIdOrNamespace: string | null = null;
// Check access and retrieve data
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
if ( !await AccessesHelper.checkTeachingStaff() ) {
return;
}
members = await GitlabManager.fetchMembers(options);
if ( !members ) {
return;
}
const assignmentGetSpinner: ora.Ora = ora('Checking assignment name availability').start();
if ( await DojoBackendManager.getAssignment(options.name) ) {
assignmentGetSpinner.fail(`Assignment name "${ options.name }" is already taken. Please choose another one.`);
return;
}
assignmentGetSpinner.succeed(`Assignment name "${ options.name }" is available`);
if ( options.template ) {
templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string);
}
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) {
return;
}
}
}
// Create the assignment
{
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);
const oraInfo = (message: string) => {
ora({
text : message,
indent: 4
}).start().info();
};
oraInfo(`${ chalk.magenta('Name:') } ${ assignment.name }`);
oraInfo(`${ chalk.magenta('Web URL:') } ${ assignment.gitlabCreationInfo.web_url }`);
oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ assignment.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ chalk.magenta('SSH Repo:') } ${ assignment.gitlabCreationInfo.ssh_url_to_repo }`);
} catch ( error ) {
return;
}
}
}
}
export default new AssignmentCreateCommand();
\ No newline at end of file
import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase';
class AssignmentPublishCommand extends AssignmentPublishUnpublishCommandBase {
protected commandName: string = 'publish';
protected publish: boolean = true;
}
export default new AssignmentPublishCommand();
\ No newline at end of file
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 {
protected abstract publish: boolean;
protected defineCommand() {
this.command
.description('publish an assignment')
.argument('<name or url>', 'name or url (http/s or ssh) of the assignment')
.option('-f, --force', 'don\'t ask for confirmation')
.action(this.commandAction.bind(this));
}
protected async commandAction(assignmentNameOrUrl: string, options: { force: boolean }): Promise<void> {
if ( !options.force ) {
options.force = (await inquirer.prompt({
type : 'confirm',
name : 'force',
message: this.publish ? 'Are you sure you want to publish this assignment?' : 'Are you sure you want to unpublish this assignment?'
})).force;
}
if ( !options.force ) {
return;
}
let assignment!: Assignment | undefined;
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
if ( !await SessionManager.testSession(true, null) ) {
return;
}
ora('Checking assignment:').start().info();
ora({
text : assignmentNameOrUrl,
indent: 4
}).start().info();
const assignmentGetSpinner: ora.Ora = ora({
text : 'Checking if assignment exists',
indent: 8
}).start();
assignment = await DojoBackendManager.getAssignment(assignmentNameOrUrl);
if ( !assignment ) {
assignmentGetSpinner.fail(`The assignment doesn't exists`);
return;
}
assignmentGetSpinner.succeed(`The assignment exists`);
const assignmentCheckAccessSpinner: ora.Ora = ora({
text : 'Checking accesses',
indent: 8
}).start();
if ( !assignment.staff.some(staff => staff.id === SessionManager.profile?.id) ) {
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`);
}
{
console.log(chalk.cyan(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish);
} catch ( error ) {
return;
}
}
}
}
export default AssignmentPublishUnpublishCommandBase;
\ No newline at end of file
import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase';
class AssignmentUnpublishCommand extends AssignmentPublishUnpublishCommandBase {
protected commandName: string = 'unpublish';
protected publish: boolean = false;
}
export default new AssignmentUnpublishCommand();
\ No newline at end of file
import CommanderCommand from '../CommanderCommand';
import ExerciseCreateCommand from './subcommands/ExerciseCreateCommand';
import ExerciseRunCommand from './subcommands/ExerciseRunCommand';
class ExerciseCommand extends CommanderCommand {
protected commandName: string = 'exercise';
protected defineCommand() {
this.command
.description('manage an exercise');
}
protected defineSubCommands() {
ExerciseCreateCommand.registerOnCommand(this.command);
ExerciseRunCommand.registerOnCommand(this.command);
}
protected async commandAction(options: any): Promise<void> { }
}
export default new ExerciseCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import chalk from 'chalk';
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';
class ExerciseCreateCommand extends CommanderCommand {
protected commandName: string = 'create';
protected defineCommand() {
this.command
.description('create a new exercise from an assignment')
.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')
.action(this.commandAction.bind(this));
}
protected async commandAction(options: any): Promise<void> {
let members!: Array<GitlabUser> | false;
let assignment!: Assignment | undefined;
// Check access and retrieve data
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
if ( !await AccessesHelper.checkStudent() ) {
return;
}
members = await GitlabManager.fetchMembers(options);
if ( !members ) {
return;
}
ora('Checking assignment:').start().info();
const assignmentGetSpinner: ora.Ora = ora({
text : 'Checking if assignment exists',
indent: 4
}).start();
assignment = await DojoBackendManager.getAssignment(options.assignment);
if ( !assignment ) {
assignmentGetSpinner.fail(`Assignment "${ options.assignment }" doesn't exists`);
return;
}
assignmentGetSpinner.succeed(`Assignment "${ options.assignment }" exists`);
const assignmentPublishedSpinner: ora.Ora = ora({
text : 'Checking if assignment is published',
indent: 4
}).start();
if ( !assignment.published ) {
assignmentPublishedSpinner.fail(`Assignment "${ assignment.name }" isn't published`);
return;
}
assignmentPublishedSpinner.succeed(`Assignment "${ assignment.name }" is published`);
}
//Create the exercise
{
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);
const oraInfo = (message: string) => {
ora({
text : message,
indent: 4
}).start().info();
};
oraInfo(`${ chalk.magenta('Id:') } ${ exercise.id }`);
oraInfo(`${ chalk.magenta('Name:') } ${ exercise.name }`);
oraInfo(`${ chalk.magenta('Web URL:') } ${ exercise.gitlabCreationInfo.web_url }`);
oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ exercise.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ chalk.magenta('SSH Repo:') } ${ exercise.gitlabCreationInfo.ssh_url_to_repo }`);
} catch ( error ) {
return;
}
}
}
}
export default new ExerciseCreateCommand();
\ No newline at end of file
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);
class ExerciseRunCommand extends CommanderCommand {
protected commandName: string = 'run';
private readonly dateISOString: string = (new Date()).toISOString().replace(/:/g, '_').replace(/\./g, '_');
private readonly folderResultsVolume: string = path.join(os.homedir(), 'DojoExecutions', `dojo_execLogs_${ this.dateISOString }`);
private readonly folderResultsDojo: string = path.join(this.folderResultsVolume, `Dojo/`);
private readonly folderResultsExercise: string = path.join(this.folderResultsVolume, `Exercise/`);
private readonly projectName: string = `${ ClientsSharedConfig.dockerCompose.projectName }_${ this.dateISOString.toLowerCase() }`;
private readonly fileComposeLogs: string = path.join(this.folderResultsDojo, `dockerComposeLogs.txt`);
protected defineCommand() {
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
}).start().info();
}
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: ExerciseResultsSanitizerAndValidator;
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...'));
// Create result temp folder
fs.mkdirSync(this.folderResultsVolume, { recursive: true });
fs.mkdirSync(this.folderResultsDojo, { recursive: true });
fs.mkdirSync(this.folderResultsExercise, { recursive: true });
ora({
text : `Checking exercise content:`,
indent: 4
}).start().info();
// Exercise folder
{
const spinner: ora.Ora = ora({
text : `Checking exercise folder`,
indent: 8
}).start();
const files = fs.readdirSync(options.path);
const missingFiles = Config.exercise.neededFiles.map((file: string): [ string, boolean ] => [ file, files.includes(file) ]).filter((file: [ string, boolean ]) => !file[1]);
if ( missingFiles.length > 0 ) {
spinner.fail(`The exercise folder is missing the following files: ${ missingFiles.map((file: [ string, boolean ]) => file[0]).join(', ') }`);
return;
}
spinner.succeed(`The exercise folder contains all the needed files`);
}
// dojo_assignment.json validity
{
const spinner: ora.Ora = ora({
text : `Checking ${ ClientsSharedConfig.assignment.filename } file`,
indent: 8
}).start();
const validationResults = SharedAssignmentHelper.validateDescriptionFile(path.join(options.path, ClientsSharedConfig.assignment.filename));
if ( !validationResults.isValid ) {
spinner.fail(`The ${ ClientsSharedConfig.assignment.filename } file is invalid: ${ JSON.stringify(validationResults.errors) }`);
return;
} else {
assignmentFile = validationResults.results!;
}
haveResultsVolume = assignmentFile.result.volume !== undefined;
spinner.succeed(`The ${ ClientsSharedConfig.assignment.filename } file is valid`);
}
// Docker daemon
{
const spinner: ora.Ora = ora({
text : `Checking Docker daemon`,
indent: 4
}).start();
try {
await execAsync(`cd "${ Config.folders.defaultLocalExercise }";docker ps`);
} catch ( error ) {
spinner.fail(`The Docker daemon is not running`);
return;
}
spinner.succeed(`The Docker daemon is running`);
}
}
// Step 2: Run docker-compose file
{
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);
composeFileOverride = [ 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
}).start();
if ( options.verbose && name == 'COMPOSE_RUN' ) {
spinner.info();
}
});
exerciseDockerCompose.events.on('endStep', (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);
}
}
});
exerciseDockerCompose.events.on('finished', (success: boolean, exitCode: number) => {
success ? resolve() : reject();
});
exerciseDockerCompose.run(true);
});
} catch ( error ) { }
fs.rmSync(composeOverridePath, { force: true });
fs.writeFileSync(this.fileComposeLogs, exerciseDockerCompose.allLogs);
if ( !exerciseDockerCompose.success ) {
this.displayExecutionLogs();
return;
}
}
// Step 3: Get results
{
console.log(chalk.cyan('Please wait while we are checking the results...'));
exerciseResultsValidation = new ExerciseResultsSanitizerAndValidator(this.folderResultsDojo, this.folderResultsExercise, exerciseDockerCompose.exitCode);
try {
await new Promise<void>((resolve, reject) => {
let spinner: ora.Ora;
exerciseResultsValidation.events.on('step', (name: string, message: string) => {
spinner = ora({
text : message,
indent: 4
}).start();
});
exerciseResultsValidation.events.on('endStep', (stepName: string, message: string, error: boolean) => {
if ( error ) {
if ( stepName == 'CHECK_SIZE' ) {
spinner.warn(message);
} else {
spinner.fail(message);
}
} else {
spinner.succeed(message);
}
});
exerciseResultsValidation.events.on('finished', (success: boolean, exitCode: number) => {
success || exitCode == ExerciseCheckerError.EXERCISE_RESULTS_FOLDER_TOO_BIG ? resolve() : reject();
});
exerciseResultsValidation.run();
});
} catch ( error ) {
this.displayExecutionLogs();
return;
}
}
// Step 4: Display results + Volume location
{
ClientsSharedExerciseHelper.displayExecutionResults(exerciseResultsValidation.exerciseResults!, exerciseDockerCompose.exitCode, {
INFO : chalk.bold,
SUCCESS: chalk.green,
FAILURE: chalk.red
}, `\n\n${ chalk.bold('Execution results folder') } : ${ this.folderResultsVolume }`);
}
}
}
export default new ExerciseRunCommand();
\ No newline at end of file
import CommanderCommand from '../CommanderCommand';
import SessionTestCommand from './subcommands/SessionTestCommand';
import SessionLoginCommand from './subcommands/SessionLoginCommand';
import SessionLogoutCommand from './subcommands/SessionLogoutCommand';
class SessionCommand extends CommanderCommand {
protected commandName: string = 'session';
protected defineCommand() {
this.command
.description('manage Dojo and Gitlab sessions');
}
protected defineSubCommands() {
SessionLoginCommand.registerOnCommand(this.command);
SessionLogoutCommand.registerOnCommand(this.command);
SessionTestCommand.registerOnCommand(this.command);
}
protected async commandAction(options: any): Promise<void> { }
}
export default new SessionCommand();
\ No newline at end of file
import chalk from 'chalk';
import CommanderCommand from '../../CommanderCommand';
import SessionManager from '../../../managers/SessionManager';
class SessionLoginCommand extends CommanderCommand {
protected commandName: string = 'login';
protected defineCommand() {
this.command
.description('login to Dojo')
.option('-c, --cli', 'proceed to the login in headless mode (do not try to open web browser).')
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { cli: boolean }): Promise<void> {
try {
console.log(chalk.cyan('Please wait while we login you into Dojo...'));
await SessionManager.login(options.cli);
} catch ( error ) {
}
}
}
export default new SessionLoginCommand();
\ No newline at end of file
import CommanderCommand from '../../CommanderCommand';
import inquirer from 'inquirer';
import ora from 'ora';
import SessionManager from '../../../managers/SessionManager';
class SessionLogoutCommand extends CommanderCommand {
protected commandName: string = 'logout';
protected defineCommand() {
this.command
.description('logout of Dojo')
.option('-f, --force', 'attempt to logout without prompting for confirmation')
.action(this.commandAction.bind(this));
}
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
})).confirm;
if ( !confirm ) {
return;
}
}
const spinner: ora.Ora = ora('Please wait while we are logout you from Dojo...').start();
SessionManager.logout();
spinner.succeed('You are now logged out from Dojo.');
}
}
export default new SessionLogoutCommand();
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please to comment