Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • Jw_sonar_backup
  • add_route_assignments
  • add_route_user
  • assignment_filter
  • bedran_exercise-list
  • exercise_list_filter
  • interactive-mode-preference
  • jw_sonar
  • main
  • move-to-esm-only
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.2.0
  • 3.2.2
  • 3.2.3
  • 3.3.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 4.0.0
  • 4.0.1
  • 4.1.0
  • 4.1.1
  • 4.2.0
  • 5.0.0
  • 6.0.0-dev
  • Latest
  • Pre-alpha
  • v1.0.1
36 results

Target

Select target project
  • Dojo_Project_Nguyen/ui/dojocli
  • dojo_project/projects/ui/dojocli
  • tom.andrivet/dojocli
  • orestis.malaspin/dojocli
4 results
Select Git revision
  • Jw_sonar_backup
  • add_route_assignments
  • add_route_user
  • assignment_filter
  • bedran_exercise-list
  • exercise_list_filter
  • interactive-mode-preference
  • jw_sonar
  • main
  • move-to-esm-only
  • v6.0.0
  • 2.0.0
  • 2.1.0
  • 2.2.0
  • 3.0.0
  • 3.0.1
  • 3.1.0
  • 3.1.1
  • 3.1.2
  • 3.2.0
  • 3.2.2
  • 3.2.3
  • 3.3.0
  • 3.4.1
  • 3.4.2
  • 3.5.0
  • 4.0.0
  • 4.0.1
  • 4.1.0
  • 4.1.1
  • 4.2.0
  • 5.0.0
  • 6.0.0-dev
  • Latest
  • Pre-alpha
  • v1.0.1
36 results
Show changes
Showing
with 3614 additions and 990 deletions
release:wiki:
release:doc:wiki:
stage: release
tags:
- release
......@@ -44,7 +44,7 @@ release:wiki:
# Push the change back to the master branch of the wiki
- git push origin "HEAD:main"
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- when: never
release:gitlab:
......@@ -58,10 +58,10 @@ release:gitlab:
- RELEASE_NAME=$VERSION
- !reference [ .release_gitlab, script ]
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"'
- if: $CI_COMMIT_REF_PROTECTED == "true"
release:latest:gitlab:
release:gitlab:latest:
stage: release
tags:
- release
......@@ -72,10 +72,10 @@ release:latest:gitlab:
- RELEASE_NAME="Latest"
- !reference [ .release_gitlab, script ]
rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
release:pre-alpha:gitlab:
release:gitlab:pre-alpha:
stage: release
tags:
- release
......@@ -86,4 +86,6 @@ release:pre-alpha:gitlab:
- RELEASE_NAME="Pre-alpha"
- !reference [ .release_gitlab, script ]
rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
\ No newline at end of file
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: never
- if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
......@@ -6,3 +6,5 @@
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
......@@ -12,4 +12,7 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="7e31b6fd-1f86-4d26-8c50-c9ab6530f54e" />
</component>
</module>
\ No newline at end of file
Subproject commit ffc5d65f9f0f0e825688177425e526131aa84631
Subproject commit ef5c7bd49a57bc28db77bad797de4980133d6523
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EslintConfiguration">
<custom-configuration-file used="false" path="./eslint.config.js" />
<option name="fix-on-save" value="true" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MaterialThemeProjectNewConfig">
<option name="metadata">
<MTProjectMetadataState>
<option name="migrated" value="true" />
<option name="pristineConfig" value="false" />
<option name="userId" value="104e8585:19002424fea:-7ffe" />
</MTProjectMetadataState>
</option>
</component>
</project>
\ No newline at end of file
......@@ -2,6 +2,7 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
<mapping directory="$PROJECT_DIR$/.idea/jetbrainsConfiguration" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/shared" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" />
</component>
......
// @ts-check
// @formatter:off
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
export default tseslint.config({
ignores: [ 'dist/*', 'node_modules/*', '.gitlab-ci.yml', 'eslint.config.mjs' ]
}, eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, {
languageOptions: {
parserOptions: {
project: true, tsconfigRootDir: import.meta.dirname
}
}
}, {
plugins: {
'@typescript-eslint': tseslint.plugin
}, rules: {
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/require-await': 'off',
'@typescript-eslint/restrict-template-expressions': 'off',
'@typescript-eslint/no-floating-promises': 'off',
}
});
\ No newline at end of file
This diff is collapsed.
{
"name" : "dojo_cli",
"description" : "CLI of the Dojo project",
"version" : "3.5.0",
"version" : "6.0.0",
"license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>",
"author" : "Michaël Minelli <dojo@mail.minelli.swiss>",
"main" : "dist/app.js",
"bin" : {
"dojo": "./dist/app.js"
......@@ -25,50 +25,58 @@
]
},
"scripts" : {
"dotenv:build": "npx dotenv-vault local build",
"dotenv:build": "npx dotenvx encrypt",
"lint" : "npx eslint .",
"genversion" : "npx genversion -s -e src/config/Version.ts",
"build" : "npm run genversion; npx tsc",
"start:dev" : "npm run genversion; npm run lint; npx ts-node src/app.ts",
"start:dev" : "npm run genversion; npm run lint; tsc --noEmit && npx tsx --no-warnings src/app.ts",
"test" : "echo \"Error: no test specified\" && exit 1"
},
"dependencies" : {
"@gitbeaker/rest" : "^39.34.3",
"@dotenvx/dotenvx" : "^0.45.0",
"@eslint/js" : "^9.21.0",
"@gitbeaker/core" : "^42.1.0",
"@gitbeaker/requester-utils": "^42.1.0",
"@gitbeaker/rest" : "^42.1.0",
"appdata-path" : "^1.0.0",
"axios" : "^1.6.5",
"axios" : "1.7.8",
"boxen" : "^5.1.2",
"chalk" : "^4.1.2",
"commander" : "^11.1.0",
"dotenv" : "^16.3.1",
"dotenv-expand" : "^10.0.0",
"fs-extra" : "^11.2.0",
"cli-table3" : "^0.6.5",
"commander" : "^13.1.0",
"form-data" : "^4.0.2",
"fs-extra" : "^11.3.0",
"fuse.js" : "^7.1.0",
"http-status-codes" : "^2.3.0",
"inquirer" : "^8.2.6",
"json5" : "^2.2.3",
"jsonwebtoken" : "^8.5.1",
"open" : "^8.4.2",
"ora" : "^5.4.1",
"semver" : "^7.5.4",
"tar-stream" : "^3.1.6",
"winston" : "^3.11.0",
"yaml" : "^2.3.4",
"zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
"semver" : "^7.7.1",
"tar-stream" : "^3.1.7",
"winston" : "^3.17.0",
"winston-transport" : "^4.9.0",
"yaml" : "^2.7.0",
"zod" : "^3.24.2",
"zod-validation-error" : "^3.4.0"
},
"devDependencies": {
"@types/fs-extra" : "^11.0.4",
"@types/inquirer" : "^8.2.10",
"@types/jsonwebtoken" : "^8.5.9",
"@types/node" : "^18.19.2",
"@types/semver" : "^7.5.6",
"@types/jsonwebtoken" : "^9.0.9",
"@types/node" : "^18.19.76",
"@types/semver" : "^7.5.8",
"@types/tar-stream" : "^3.1.3",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser" : "^6.18.1",
"dotenv-vault" : "^1.25.0",
"@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser" : "^8.25.0",
"dotenv-cli" : "^8.0.0",
"dotenv-vault" : "^1.26.2",
"genversion" : "^3.2.0",
"pkg" : "^5.8.1",
"tiny-typed-emitter" : "^2.1.0",
"ts-node" : "^10.9.2",
"typescript" : "^5.3.3"
"tsx" : "^4.19.3",
"typescript" : "~5.5.4",
"typescript-eslint" : "^7.18.0"
}
}
// Read from the .env file
// ATTENTION : These lines MUST be the first of this file (except for the path import)
import path = require('node:path');
import myEnv = require('dotenv');
import dotenvExpand = require('dotenv-expand');
// ATTENTION : This line MUST be the first of this file
import './init.js';
import CommanderApp from './commander/CommanderApp.js';
import HttpManager from './managers/HttpManager.js';
import Config from './config/Config';
import ConfigFiles from './config/ConfigFiles';
import DojoBackendManager from './managers/DojoBackendManager';
import SharedAssignmentHelper from './shared/helpers/Dojo/SharedAssignmentHelper';
dotenvExpand.expand(myEnv.config({
path : path.join(__dirname, '../.env'),
DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development'
}));
(async () => {
ConfigFiles.init();
require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file
import CommanderApp from './commander/CommanderApp';
import HttpManager from './managers/HttpManager';
await Config.init(await DojoBackendManager.getApiUrl());
SharedAssignmentHelper.init(Config.gitlabManager);
HttpManager.registerAxiosInterceptor();
new CommanderApp();
\ No newline at end of file
const commanderApp = new CommanderApp();
await commanderApp.init();
commanderApp.parse();
})();
\ No newline at end of file
import { Command, Option } from 'commander';
import SessionCommand from './session/SessionCommand';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import AssignmentCommand from './assignment/AssignmentCommand';
import ExerciseCommand from './exercise/ExerciseCommand';
import SharedConfig from '../shared/config/SharedConfig';
import AssignmentCommand from './assignment/AssignmentCommand.js';
import ExerciseCommand from './exercise/ExerciseCommand.js';
import SharedConfig from '../shared/config/SharedConfig.js';
import boxen from 'boxen';
import { stateConfigFile } from '../config/ConfigFiles';
import semver from 'semver/preload';
import { version } from '../config/Version';
import semver from 'semver/preload.js';
import { version } from '../config/Version.js';
import CompletionCommand from './completion/CompletionCommand.js';
import AuthCommand from './auth/AuthCommand.js';
import SessionCommand from './auth/SessionCommand.js';
import UpgradeCommand from './UpgradeCommand.js';
import TextStyle from '../types/TextStyle.js';
import TagCommand from './tag/TagCommand';
import ConfigFiles from '../config/ConfigFiles';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig';
import Config from '../config/Config';
import SettingsCommand from './settings/SettingsCommand';
class CommanderApp {
program: Command = new Command();
public program: Command = new Command();
private readonly commandHookDisabled: Array<string> = [ 'completion' ];
private hasToExecuteHook(actionCommand: Command): boolean {
if ( actionCommand.parent == null ) {
return true;
} else {
if ( this.commandHookDisabled.includes(actionCommand.name()) ) {
return false;
}
constructor() {
return this.hasToExecuteHook(actionCommand.parent);
}
}
public async init() {
this.program
.name('dojo')
.description('CLI of the Dojo application')
......@@ -26,20 +46,37 @@ class CommanderApp {
sortSubcommands : true
})
.option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL)
.option('-I, --interactive', 'show interactive interface when available', Config.interactiveMode)
.addOption(new Option('--debug').hideHelp())
.hook('preAction', () => {
.hook('preAction', (_thisCommand: Command, actionCommand: Command) => {
if ( this.hasToExecuteHook(actionCommand) ) {
this.warnDevelopmentVersion();
}).hook('postAction', () => {
}
})
.hook('postAction', (_thisCommand: Command, actionCommand: Command) => {
if ( this.hasToExecuteHook(actionCommand) ) {
this.informNewVersion();
}
});
const interactiveModeOption = new Option('-I, --interactive', 'show interactive interface when available');
const noInteractiveModeOption = new Option('-N, --no_interactive', 'hide interactive interface when available').conflicts('interactive'); // WARNING: Due to a bug in commander.js, the conflicts method doesn't work as expected if the command is named no-interactive
if ( Config.interactiveMode ) {
interactiveModeOption.hideHelp();
} else {
noInteractiveModeOption.hideHelp();
}
this.program.addOption(interactiveModeOption).addOption(noInteractiveModeOption);
this.program.on('option:host', () => {
ClientsSharedConfig.apiURL = this.program.opts().host;
});
this.program.on('option:no_interactive', () => {
Config.interactiveMode = false;
});
this.program.on('option:interactive', () => {
Config.interactiveMode = this.program.opts().interactive;
Config.interactiveMode = true;
});
this.program.on('option:debug', () => {
......@@ -47,15 +84,17 @@ class CommanderApp {
});
this.registerCommands();
}
public parse() {
this.program.parse();
}
private warnDevelopmentVersion() {
if ( !SharedConfig.production ) {
console.log(boxen(`This is a development (unstable) version of the DojoCLI.
If you want to use the stable version, please install the package from the following url:
https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
If you want to use the stable version, please execute this command:
${ TextStyle.CODE(' dojo upgrade ') }`, {
title : 'Warning',
titleAlignment: 'center',
borderColor : 'red',
......@@ -69,12 +108,12 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
private informNewVersion() {
if ( SharedConfig.production ) {
const latestDojoCliVersion = stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0';
const latestDojoCliVersionNotification = stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0;
if ( semver.lt(version, latestDojoCliVersion) ) {
if ( (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available:
https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
const latestDojoCliVersion = ConfigFiles.stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0';
const latestDojoCliVersionNotification = ConfigFiles.stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0;
if ( semver.lt(version as string, latestDojoCliVersion) && (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available.
You can upgrade the DojoCLI by executing this command:
${ TextStyle.CODE(' dojo upgrade ') }`, {
title : 'Information',
titleAlignment: 'center',
borderColor : 'blue',
......@@ -83,16 +122,20 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
padding : 1,
textAlignment : 'left'
}));
stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
}
ConfigFiles.stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
}
}
}
private registerCommands() {
new AuthCommand().registerOnCommand(this.program);
SessionCommand.registerOnCommand(this.program);
AssignmentCommand.registerOnCommand(this.program);
ExerciseCommand.registerOnCommand(this.program);
TagCommand.registerOnCommand(this.program);
CompletionCommand.registerOnCommand(this.program);
UpgradeCommand.registerOnCommand(this.program);
SettingsCommand.registerOnCommand(this.program);
}
}
......
import { Command } from 'commander';
import { Command, CommandOptions } from 'commander';
abstract class CommanderCommand {
protected abstract commandName: string;
protected aliasNames: Array<string> = [];
protected options: CommandOptions = {};
command: Command = new Command();
registerOnCommand(parent: Command) {
this.command = parent.command(this.commandName);
this.command = parent.command(this.commandName, this.options).aliases(this.aliasNames);
this.defineCommand();
this.defineSubCommands();
......@@ -14,7 +18,12 @@ abstract class CommanderCommand {
protected abstract defineCommand(): void;
protected defineSubCommands() {}
protected defineSubCommands() {
/*
* No action
* Override this method only if you need to define subcommands
* */
}
protected abstract commandAction(...args: Array<unknown>): Promise<void>;
}
......
import CommanderCommand from './CommanderCommand.js';
import ora from 'ora';
import TextStyle from '../types/TextStyle.js';
import os from 'os';
import { spawn } from 'child_process';
import Config from '../config/Config';
class UpgradeCommand extends CommanderCommand {
protected commandName: string = 'upgrade';
protected defineCommand() {
this.command.description('upgrade the DojoCLI.')
.option('--pre-alpha', 'upgrade to the latest pre-alpha (dev) version. Attention it\'s an unstable version without support.')
.option('-o, --instructions', 'only display upgrade instructions.')
.action(this.commandAction.bind(this));
}
private displayInstructions(upgradeCommand: string, preAlpha: boolean) {
upgradeCommand = TextStyle.CODE(` ${ upgradeCommand } `);
console.log(TextStyle.BLOCK(`You can upgrade DojoCLI on ${ os.platform() === 'darwin' ? 'macOS' : 'Linux' }:`));
ora().start().info(`By executing this command: ${ upgradeCommand }`);
console.log('or');
ora().start().info(`By downloading the release from: ${ preAlpha ? Config.gitlab.cliPreAlphaReleasePage : Config.gitlab.cliReleasePage }`);
}
private upgrade(upgradeCommand: string) {
const upgradeSpinner = ora().start('DojoCLI upgrade is in progress. Please wait...');
const upgradeProcess = spawn(upgradeCommand, {
shell: true
});
upgradeProcess.on('exit', code => {
code === null || code !== 0 ? upgradeSpinner.fail('DojoCLI upgrade failed... Please try again manually.') : upgradeSpinner.succeed('DojoCLI upgrade is successful.');
});
}
protected async commandAction(options: { preAlpha: boolean, instructions: boolean }): Promise<void> {
if ( os.platform() === 'win32' ) {
if ( !options.instructions ) {
ora().start().warn('Automatic upgrade is not supported on Windows. \n');
}
console.log(TextStyle.BLOCK('You can upgrade DojoCLI on Windows:'));
ora().start().info(`By downloading the release from: ${ options.preAlpha ? Config.gitlab.cliPreAlphaReleasePage : Config.gitlab.cliReleasePage }`);
return;
}
const upgradeCommand = `curl -L "https://dojo.isc-hesge.ch/installer.sh" | sh /dev/stdin${ options.preAlpha ? ' installer=pre-alpha' : '' }`;
if ( options.instructions ) {
this.displayInstructions(upgradeCommand, options.preAlpha);
} else {
this.upgrade(upgradeCommand);
}
}
}
export default new UpgradeCommand();
\ 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';
import AssignmentRunCommand from './subcommands/AssignmentRunCommand';
import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand';
import CommanderCommand from '../CommanderCommand.js';
import AssignmentCreateCommand from './subcommands/AssignmentCreateCommand.js';
import AssignmentPublishCommand from './subcommands/AssignmentPublishCommand.js';
import AssignmentUnpublishCommand from './subcommands/AssignmentUnpublishCommand.js';
import AssignmentCheckCommand from './subcommands/AssignmentCheckCommand.js';
import AssignmentRunCommand from './subcommands/AssignmentRunCommand.js';
import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand.js';
class AssignmentCommand extends CommanderCommand {
......@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand {
AssignmentCorrectionCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
import CommanderCommand from '../../CommanderCommand';
import Config from '../../../config/Config';
import CommanderCommand from '../../CommanderCommand.js';
import Config from '../../../config/Config.js';
import ora from 'ora';
import chalk from 'chalk';
import AssignmentValidator from '../../../sharedByClients/helpers/Dojo/AssignmentValidator';
import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper';
import { Option } from 'commander';
import SharedConfig from '../../../shared/config/SharedConfig';
import AssignmentValidator from '../../../sharedByClients/helpers/Dojo/AssignmentValidator.js';
import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper.js';
import SharedConfig from '../../../shared/config/SharedConfig.js';
import GlobalHelper from '../../../helpers/GlobalHelper.js';
class AssignmentCheckCommand extends CommanderCommand {
protected commandName: string = 'check';
protected currentSpinner: ora.Ora = ora();
private verbose: boolean = false;
private superVerbose: boolean = false;
private buildPhase: boolean | undefined = undefined;
protected defineCommand() {
this.command
GlobalHelper.runCommandDefinition(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 principal container output in live')
.addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
.addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }))
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
const verbose: boolean = options.verbose || options.superVerbose || SharedConfig.debug;
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 ( verbose ) {
let buildPhase: boolean | undefined = undefined;
assignmentValidator.events.on('logs', (log: string, error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) => {
private logsEvent(log: string, _error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) {
for ( const line of log.split('\n') ) {
if ( displayable && buildPhase == undefined && line.startsWith('#') ) {
buildPhase = true;
if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
this.buildPhase = true;
}
if ( currentSubStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) {
buildPhase = false;
if ( currentSubStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
this.buildPhase = false;
}
if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) {
if ( SharedConfig.debug || (displayable && (this.superVerbose || this.buildPhase === false)) ) {
console.log(line);
}
}
});
}
assignmentValidator.events.on('step', (name: string, message: string) => {
console.log(chalk.cyan(message));
});
assignmentValidator.events.on('subStep', (name: string, message: string) => {
spinner = ora({
private subStepEvent(name: string, message: string) {
this.currentSpinner = ora({
text : message,
indent: 4
}).start();
if ( verbose && name == 'COMPOSE_RUN' ) {
spinner.info();
if ( this.verbose && name === 'COMPOSE_RUN' ) {
this.currentSpinner.info();
}
}
});
assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => {
private endSubStepEvent(stepName: string, message: string, error: boolean) {
if ( error ) {
if ( verbose && stepName == 'COMPOSE_RUN' ) {
if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().fail();
} else {
spinner.fail(message);
this.currentSpinner.fail(message);
}
} else {
if ( verbose && stepName == 'COMPOSE_RUN' ) {
if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().succeed();
} else {
spinner.succeed(message);
this.currentSpinner.succeed(message);
}
}
}
});
assignmentValidator.events.on('finished', (success: boolean) => {
success ? resolve() : reject();
});
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
this.superVerbose = options.superVerbose;
this.verbose = options.verbose || options.superVerbose || SharedConfig.debug;
const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
const assignmentValidator = new AssignmentValidator(localExercisePath);
try {
await new Promise<void>((resolve, reject) => {
if ( this.verbose ) {
assignmentValidator.events.on('logs', this.logsEvent.bind(this));
}
assignmentValidator.events.on('step', (_name: string, message: string) => console.log(chalk.cyan(message)));
assignmentValidator.events.on('subStep', this.subStepEvent.bind(this));
assignmentValidator.events.on('endSubStep', this.endSubStepEvent.bind(this));
assignmentValidator.events.on('finished', (success: boolean) => success ? resolve() : reject());
assignmentValidator.run(true);
assignmentValidator.run();
});
} catch ( error ) { /* empty */ }
......
import CommanderCommand from '../../CommanderCommand';
import chalk from 'chalk';
import CommanderCommand from '../../CommanderCommand.js';
import ora from 'ora';
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 SharedConfig from '../../../shared/config/SharedConfig';
import AccessesHelper from '../../../helpers/AccessesHelper.js';
import Assignment from '../../../sharedByClients/models/Assignment.js';
import DojoBackendManager from '../../../managers/DojoBackendManager.js';
import Toolbox from '../../../shared/helpers/Toolbox.js';
import * as Gitlab from '@gitbeaker/rest';
import TextStyle from '../../../types/TextStyle.js';
import Config from '../../../config/Config';
import SharedSonarManager from '../../../shared/managers/SharedSonarManager';
import { Option } from 'commander';
type CommandOptions = { name: string, language: string, template?: string, members_id?: Array<number>, members_username?: Array<string>, clone?: string | boolean, sonar?: boolean, sonarStrict: boolean, gate?: string, profile?: string[] }
class AssignmentCreateCommand extends CommanderCommand {
protected commandName: string = 'create';
private members!: Array<Gitlab.UserSchema> | undefined;
private templateIdOrNamespace: string | null = null;
private assignment!: Assignment;
private sonar: boolean = false;
private sonarGate: string | undefined = undefined;
private sonarProfiles: string[] = [];
protected defineCommand() {
this.command
.description('create a new repository for an assignment')
.requiredOption('-n, --name <name>', 'name of the assignment')
.requiredOption('-l, --language <string>', 'main programming language 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)')
.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));
if ( SharedConfig.sonar.enabled ) {
this.command.requiredOption('-s, --sonar', 'add sonar to the code checking process for assignment and exercises')
.requiredOption('-d, --no-sonar', 'disable sonar for the code checking process for assignment and exercises')
.addOption(new Option('--sonar-strict', 'force the sonar gate to pass to validate the exercise results').default(false).implies({ sonar: true }))
.addOption(new Option('-g, --gate <gate>', 'quality gate for sonar').implies({ sonar: true }))
.addOption(new Option('-p, --profile <profile...>', 'quality profiles for sonar').default([]).implies({ sonar: true }));
}
}
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;
private async dataRetrieval(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
// Check access and retrieve data
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
await AccessesHelper.checkTeachingStaff();
if ( !await AccessesHelper.checkTeachingStaff() ) {
return;
this.members = await Config.gitlabManager.fetchMembers(options);
if ( !this.members ) {
throw new Error();
}
members = await GitlabManager.fetchMembers(options);
if ( !members ) {
return;
if ( options.sonar ) {
const assignmentGetSonarSpinner: ora.Ora = ora('Checking server sonar status').start();
this.sonar = (SharedConfig.sonar.enabled ? options.sonar ?? false : false);
if ( this.sonar && !(await DojoBackendManager.isSonarEnabled()) ) {
assignmentGetSonarSpinner.fail(`Sonar is currently not supported by the server. Disable sonar integration or try again later.`);
throw new Error();
}
assignmentGetSonarSpinner.succeed(`Sonar is supported by the server`);
this.sonarGate = options.gate;
this.sonarProfiles = options.profile ?? [];
}
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;
throw new Error();
}
assignmentGetSpinner.succeed(`Assignment name "${ options.name }" is available`);
// Dojo languages
const languagesSpinner: ora.Ora = ora('Checking language support').start();
const languages = await DojoBackendManager.getLanguages();
if ( !languages.includes(options.language) ) {
languagesSpinner.fail(`Language "${ options.language }" is not supported. Choose a supported language or "other"`);
console.log('List of supported languages:');
for ( const l of languages ) {
console.log(` - ${ l }`);
}
throw new Error();
}
languagesSpinner.succeed(`Language "${ options.language }" is supported`);
if ( (this.sonarGate ?? '') !== '' || this.sonarProfiles.length > 0 ) {
const qualitiesSpinner: ora.Ora = ora('Checking sonar qualities').start();
// SonarQube quality gate and profiles
const result = await DojoBackendManager.testSonarQualities(this.sonarGate ?? '', this.sonarProfiles);
if ( !result.valid ) {
const invalid = (result.badGate == undefined ? result.badProfiles : [ result.badGate, ...result.badProfiles ]);
qualitiesSpinner.fail(`Invalid quality gate or profiles : ${ invalid }`);
throw new Error();
}
qualitiesSpinner.succeed(`Quality gate and profiles are valid`);
}
// SonarQube languages
if ( this.sonar ) {
const sonarLang = await DojoBackendManager.getSonarLanguages();
if ( !sonarLang.includes(SharedSonarManager.mapLanguage(options.language)) ) {
languagesSpinner.fail(`Language "${ options.language }" is not supported with Sonar. Choose a supported language or disable sonar`);
throw new Error();
}
}
if ( options.template ) {
templateIdOrNamespace = options.template;
this.templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string);
if ( Number.isNaN(Number(this.templateIdOrNamespace)) ) {
this.templateIdOrNamespace = Toolbox.urlToPath(this.templateIdOrNamespace);
}
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) {
return;
if ( !await DojoBackendManager.checkTemplateAccess(this.templateIdOrNamespace) ) {
throw new Error();
}
}
}
// Create the assignment
{
console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...'));
private async createAssignment(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));
try {
assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace);
this.assignment = await DojoBackendManager.createAssignment(options.name, options.language, this.members!, this.templateIdOrNamespace, this.sonar, !options.sonarStrict, this.sonarGate, this.sonarProfiles);
const oraInfo = (message: string) => {
ora({
......@@ -75,23 +136,29 @@ class AssignmentCreateCommand extends CommanderCommand {
}).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;
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.assignment.name }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.assignment.gitlabCreationInfo.web_url }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.assignment.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.assignment.gitlabCreationInfo.ssh_url_to_repo }`);
if ( this.assignment.useSonar ) {
oraInfo(`${ TextStyle.LIST_ITEM_NAME('Sonar project:') } ${ SharedConfig.sonar.url }/dashboard?id=${ this.assignment.sonarKey }`);
}
}
// Clone the repository
{
private async cloneRepository(options: CommandOptions) {
if ( options.clone ) {
console.log(chalk.cyan('Please wait while we are cloning the repository...'));
console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
await Config.gitlabManager.cloneRepository(options.clone, this.assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
}
}
protected async commandAction(options: CommandOptions): Promise<void> {
try {
await this.dataRetrieval(options);
await this.createAssignment(options);
await this.cloneRepository(options);
} catch ( e ) { /* Do nothing */ }
}
}
......
import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase';
import AssignmentPublishUnpublishCommandBase from './AssignmentPublishUnpublishCommandBase.js';
class AssignmentPublishCommand extends AssignmentPublishUnpublishCommandBase {
......
import CommanderCommand from '../../CommanderCommand';
import CommanderCommand from '../../CommanderCommand.js';
import inquirer from 'inquirer';
import chalk from 'chalk';
import SessionManager from '../../../managers/SessionManager';
import SessionManager from '../../../managers/SessionManager.js';
import ora from 'ora';
import DojoBackendManager from '../../../managers/DojoBackendManager';
import Assignment from '../../../sharedByClients/models/Assignment';
import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper';
import DojoBackendManager from '../../../managers/DojoBackendManager.js';
import Assignment from '../../../sharedByClients/models/Assignment.js';
import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper.js';
import TextStyle from '../../../types/TextStyle.js';
abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
......@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
let assignment!: Assignment | undefined;
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
if ( !await SessionManager.testSession(true, null) ) {
return;
......@@ -82,7 +82,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
}
{
console.log(chalk.cyan(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
console.log(TextStyle.BLOCK(`Please wait while we ${ this.publish ? 'publish' : 'unpublish' } the assignment...`));
try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish);
......
import CommanderCommand from '../../CommanderCommand';
import Config from '../../../config/Config';
import { Option } from 'commander';
import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper';
import CommanderCommand from '../../CommanderCommand.js';
import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper.js';
import GlobalHelper from '../../../helpers/GlobalHelper.js';
class AssignmentRunCommand extends CommanderCommand {
protected commandName: string = 'run';
protected defineCommand() {
// This command is synced with the "exercise run" command
this.command
GlobalHelper.runCommandDefinition(this.command)
.description('locally run the assignment as an exercise')
.option('-p, --path <value>', 'exercise path', Config.folders.defaultLocalExercise)
.option('-v, --verbose', 'verbose mode - display principal container output in live')
.addOption(new Option('-w, --super-verbose', 'verbose mode - display all docker compose logs (build included) in live').conflicts('verbose'))
.addOption(new Option('--verbose-ssj2').hideHelp().implies({ superVerbose: true }))
.action(this.commandAction.bind(this));
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options);
await (new ExerciseRunHelper(options)).run();
}
}
......