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",
"appdata-path" : "^1.0.0",
"axios" : "^1.6.5",
"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",
"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"
"@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.7.8",
"boxen" : "^5.1.2",
"chalk" : "^4.1.2",
"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.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
await Config.init(await DojoBackendManager.getApiUrl());
SharedAssignmentHelper.init(Config.gitlabManager);
import CommanderApp from './commander/CommanderApp';
import HttpManager from './managers/HttpManager';
HttpManager.registerAxiosInterceptor();
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')
.version('{{VERSION}}')
.showHelpAfterError()
.configureHelp({
showGlobalOptions: true,
sortOptions : true,
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', () => {
this.warnDevelopmentVersion();
}).hook('postAction', () => {
this.informNewVersion();
});
.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)
.addOption(new Option('--debug').hideHelp())
.hook('preAction', (_thisCommand: Command, actionCommand: Command) => {
if ( this.hasToExecuteHook(actionCommand) ) {
this.warnDevelopmentVersion();
}
})
.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,30 +108,34 @@ 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`, {
title : 'Information',
titleAlignment: 'center',
borderColor : 'blue',
borderStyle : 'bold',
margin : 1,
padding : 1,
textAlignment : 'left'
}));
stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
}
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',
borderStyle : 'bold',
margin : 1,
padding : 1,
textAlignment : 'left'
}));
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 {
......@@ -12,7 +12,7 @@ class AssignmentCommand extends CommanderCommand {
protected defineCommand() {
this.command
.description('manage an assignment');
.description('manage an assignment');
}
protected defineSubCommands() {
......@@ -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
.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));
GlobalHelper.runCommandDefinition(this.command)
.description('locally run a check of an assignment')
.action(this.commandAction.bind(this));
}
private logsEvent(log: string, _error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) {
for ( const line of log.split('\n') ) {
if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
this.buildPhase = true;
}
if ( currentSubStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
this.buildPhase = false;
}
if ( SharedConfig.debug || (displayable && (this.superVerbose || this.buildPhase === false)) ) {
console.log(line);
}
}
}
private subStepEvent(name: string, message: string) {
this.currentSpinner = ora({
text : message,
indent: 4
}).start();
if ( this.verbose && name === 'COMPOSE_RUN' ) {
this.currentSpinner.info();
}
}
private endSubStepEvent(stepName: string, message: string, error: boolean) {
if ( error ) {
if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().fail();
} else {
this.currentSpinner.fail(message);
}
} else {
if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().succeed();
} else {
this.currentSpinner.succeed(message);
}
}
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
const verbose: boolean = options.verbose || options.superVerbose || SharedConfig.debug;
this.superVerbose = options.superVerbose;
this.verbose = options.verbose || options.superVerbose || SharedConfig.debug;
const localExercisePath: string = options.path ?? Config.folders.defaultLocalExercise;
......@@ -30,69 +80,19 @@ class AssignmentCheckCommand extends CommanderCommand {
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) => {
for ( const line of log.split('\n') ) {
if ( displayable && buildPhase == undefined && line.startsWith('#') ) {
buildPhase = true;
}
if ( currentSubStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) {
buildPhase = false;
}
if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) {
console.log(line);
}
}
});
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('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();
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());
if ( verbose && name == 'COMPOSE_RUN' ) {
spinner.info();
}
});
assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => {
if ( error ) {
if ( verbose && stepName == 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().fail();
} else {
spinner.fail(message);
}
} else {
if ( verbose && stepName == 'COMPOSE_RUN' ) {
ora({
text : message,
indent: 4
}).start().succeed();
} else {
spinner.succeed(message);
}
}
});
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')
.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));
.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`);
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`);
this.sonarGate = options.gate;
this.sonarProfiles = options.profile ?? [];
}
if ( options.template ) {
templateIdOrNamespace = options.template;
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.`);
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 ( Number.isNaN(Number(templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string);
}
if ( (this.sonarGate ?? '') !== '' || this.sonarProfiles.length > 0 ) {
const qualitiesSpinner: ora.Ora = ora('Checking sonar qualities').start();
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) {
return;
}
// 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`);
}
// Create the assignment
{
console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...'));
try {
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;
// 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();
}
}
// Clone the repository
{
if ( options.clone ) {
console.log(chalk.cyan('Please wait while we are cloning the repository...'));
if ( options.template ) {
this.templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(this.templateIdOrNamespace)) ) {
this.templateIdOrNamespace = Toolbox.urlToPath(this.templateIdOrNamespace);
}
await GitlabManager.cloneRepository(options.clone, assignment.gitlabCreationInfo.ssh_url_to_repo, undefined, true, 0);
if ( !await DojoBackendManager.checkTemplateAccess(this.templateIdOrNamespace) ) {
throw new Error();
}
}
}
private async createAssignment(options: CommandOptions) {
console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));
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({
text : message,
indent: 4
}).start().info();
};
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 }`);
}
}
private async cloneRepository(options: CommandOptions) {
if ( options.clone ) {
console.log(TextStyle.BLOCK('Please wait while we are cloning the repository...'));
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 {
......@@ -13,10 +13,10 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
protected defineCommand() {
this.command
.description(`${ this.publish ? 'publish' : 'unpublish' } 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));
.description(`${ this.publish ? 'publish' : 'unpublish' } 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> {
......@@ -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
.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));
GlobalHelper.runCommandDefinition(this.command)
.description('locally run the assignment as an exercise')
.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();
}
}
......