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 stage: release
tags: tags:
- release - release
...@@ -44,7 +44,7 @@ release:wiki: ...@@ -44,7 +44,7 @@ release:wiki:
# Push the change back to the master branch of the wiki # Push the change back to the master branch of the wiki
- git push origin "HEAD:main" - git push origin "HEAD:main"
rules: rules:
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' - when: never
release:gitlab: release:gitlab:
...@@ -58,10 +58,10 @@ release:gitlab: ...@@ -58,10 +58,10 @@ release:gitlab:
- RELEASE_NAME=$VERSION - RELEASE_NAME=$VERSION
- !reference [ .release_gitlab, script ] - !reference [ .release_gitlab, script ]
rules: rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true"' - if: $CI_COMMIT_REF_PROTECTED == "true"
release:latest:gitlab: release:gitlab:latest:
stage: release stage: release
tags: tags:
- release - release
...@@ -72,10 +72,10 @@ release:latest:gitlab: ...@@ -72,10 +72,10 @@ release:latest:gitlab:
- RELEASE_NAME="Latest" - RELEASE_NAME="Latest"
- !reference [ .release_gitlab, script ] - !reference [ .release_gitlab, script ]
rules: 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 stage: release
tags: tags:
- release - release
...@@ -86,4 +86,6 @@ release:pre-alpha:gitlab: ...@@ -86,4 +86,6 @@ release:pre-alpha:gitlab:
- RELEASE_NAME="Pre-alpha" - RELEASE_NAME="Pre-alpha"
- !reference [ .release_gitlab, script ] - !reference [ .release_gitlab, script ]
rules: rules:
- if: '$CI_COMMIT_REF_PROTECTED == "true" && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH' - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
\ No newline at end of file when: never
- if: $CI_COMMIT_REF_PROTECTED == "true"
\ No newline at end of file
...@@ -6,3 +6,5 @@ ...@@ -6,3 +6,5 @@
# Datasource local storage ignored files # Datasource local storage ignored files
/dataSources/ /dataSources/
/dataSources.local.xml /dataSources.local.xml
# GitHub Copilot persisted chat sessions
/copilot/chatSessions
...@@ -12,4 +12,7 @@ ...@@ -12,4 +12,7 @@
<orderEntry type="inheritedJdk" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
<component name="SonarLintModuleSettings">
<option name="uniqueId" value="7e31b6fd-1f86-4d26-8c50-c9ab6530f54e" />
</component>
</module> </module>
\ No newline at end of file
Subproject commit ffc5d65f9f0f0e825688177425e526131aa84631 Subproject commit ef5c7bd49a57bc28db77bad797de4980133d6523
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="EslintConfiguration"> <component name="EslintConfiguration">
<custom-configuration-file used="false" path="./eslint.config.js" />
<option name="fix-on-save" value="true" /> <option name="fix-on-save" value="true" />
</component> </component>
</project> </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 @@ ...@@ -2,6 +2,7 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$/.." vcs="Git" /> <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/shared" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" /> <mapping directory="$PROJECT_DIR$/src/sharedByClients" vcs="Git" />
</component> </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
Source diff could not be displayed: it is too large. Options to address this: view the blob.
{ {
"name" : "dojo_cli", "name" : "dojo_cli",
"description" : "CLI of the Dojo project", "description" : "CLI of the Dojo project",
"version" : "3.5.0", "version" : "6.0.0",
"license" : "AGPLv3", "license" : "AGPLv3",
"author" : "Michaël Minelli <dojo@minelli.me>", "author" : "Michaël Minelli <dojo@mail.minelli.swiss>",
"main" : "dist/app.js", "main" : "dist/app.js",
"bin" : { "bin" : {
"dojo": "./dist/app.js" "dojo": "./dist/app.js"
...@@ -25,50 +25,58 @@ ...@@ -25,50 +25,58 @@
] ]
}, },
"scripts" : { "scripts" : {
"dotenv:build": "npx dotenv-vault local build", "dotenv:build": "npx dotenvx encrypt",
"lint" : "npx eslint .", "lint" : "npx eslint .",
"genversion" : "npx genversion -s -e src/config/Version.ts", "genversion" : "npx genversion -s -e src/config/Version.ts",
"build" : "npm run genversion; npx tsc", "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" "test" : "echo \"Error: no test specified\" && exit 1"
}, },
"dependencies" : { "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", "appdata-path" : "^1.0.0",
"axios" : "^1.6.5", "axios" : "1.7.8",
"boxen" : "^5.1.2", "boxen" : "^5.1.2",
"chalk" : "^4.1.2", "chalk" : "^4.1.2",
"commander" : "^11.1.0", "cli-table3" : "^0.6.5",
"dotenv" : "^16.3.1", "commander" : "^13.1.0",
"dotenv-expand" : "^10.0.0", "form-data" : "^4.0.2",
"fs-extra" : "^11.2.0", "fs-extra" : "^11.3.0",
"fuse.js" : "^7.1.0",
"http-status-codes" : "^2.3.0", "http-status-codes" : "^2.3.0",
"inquirer" : "^8.2.6", "inquirer" : "^8.2.6",
"json5" : "^2.2.3", "json5" : "^2.2.3",
"jsonwebtoken" : "^8.5.1", "jsonwebtoken" : "^8.5.1",
"open" : "^8.4.2", "open" : "^8.4.2",
"ora" : "^5.4.1", "ora" : "^5.4.1",
"semver" : "^7.5.4", "semver" : "^7.7.1",
"tar-stream" : "^3.1.6", "tar-stream" : "^3.1.7",
"winston" : "^3.11.0", "winston" : "^3.17.0",
"yaml" : "^2.3.4", "winston-transport" : "^4.9.0",
"zod" : "^3.22.4", "yaml" : "^2.7.0",
"zod-validation-error": "^3.0.0" "zod" : "^3.24.2",
"zod-validation-error" : "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra" : "^11.0.4", "@types/fs-extra" : "^11.0.4",
"@types/inquirer" : "^8.2.10", "@types/inquirer" : "^8.2.10",
"@types/jsonwebtoken" : "^8.5.9", "@types/jsonwebtoken" : "^9.0.9",
"@types/node" : "^18.19.2", "@types/node" : "^18.19.76",
"@types/semver" : "^7.5.6", "@types/semver" : "^7.5.8",
"@types/tar-stream" : "^3.1.3", "@types/tar-stream" : "^3.1.3",
"@typescript-eslint/eslint-plugin": "^6.18.1", "@typescript-eslint/eslint-plugin": "^8.25.0",
"@typescript-eslint/parser" : "^6.18.1", "@typescript-eslint/parser" : "^8.25.0",
"dotenv-vault" : "^1.25.0", "dotenv-cli" : "^8.0.0",
"dotenv-vault" : "^1.26.2",
"genversion" : "^3.2.0", "genversion" : "^3.2.0",
"pkg" : "^5.8.1", "pkg" : "^5.8.1",
"tiny-typed-emitter" : "^2.1.0", "tiny-typed-emitter" : "^2.1.0",
"ts-node" : "^10.9.2", "tsx" : "^4.19.3",
"typescript" : "^5.3.3" "typescript" : "~5.5.4",
"typescript-eslint" : "^7.18.0"
} }
} }
// Read from the .env file // ATTENTION : This line MUST be the first of this file
// ATTENTION : These lines MUST be the first of this file (except for the path import) import './init.js';
import path = require('node:path'); import CommanderApp from './commander/CommanderApp.js';
import myEnv = require('dotenv'); import HttpManager from './managers/HttpManager.js';
import dotenvExpand = require('dotenv-expand'); 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({ (async () => {
path : path.join(__dirname, '../.env'), ConfigFiles.init();
DOTENV_KEY: 'dotenv://:key_fc323d8e0a02349342f1c6a119bb38495958ce3a43a87d19a3f674b7e2896dcb@dotenv.local/vault/.env.vault?environment=development'
}));
require('./shared/helpers/TypeScriptExtensions'); // ATTENTION : This line MUST be the second of this file await Config.init(await DojoBackendManager.getApiUrl());
import CommanderApp from './commander/CommanderApp';
import HttpManager from './managers/HttpManager';
SharedAssignmentHelper.init(Config.gitlabManager);
HttpManager.registerAxiosInterceptor(); HttpManager.registerAxiosInterceptor();
const commanderApp = new CommanderApp();
new CommanderApp(); await commanderApp.init();
\ No newline at end of file commanderApp.parse();
})();
\ No newline at end of file
import { Command, Option } from 'commander'; import { Command, Option } from 'commander';
import SessionCommand from './session/SessionCommand'; import AssignmentCommand from './assignment/AssignmentCommand.js';
import ClientsSharedConfig from '../sharedByClients/config/ClientsSharedConfig'; import ExerciseCommand from './exercise/ExerciseCommand.js';
import AssignmentCommand from './assignment/AssignmentCommand'; import SharedConfig from '../shared/config/SharedConfig.js';
import ExerciseCommand from './exercise/ExerciseCommand';
import SharedConfig from '../shared/config/SharedConfig';
import boxen from 'boxen'; import boxen from 'boxen';
import { stateConfigFile } from '../config/ConfigFiles'; import semver from 'semver/preload.js';
import semver from 'semver/preload'; import { version } from '../config/Version.js';
import { version } from '../config/Version'; 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 Config from '../config/Config';
import SettingsCommand from './settings/SettingsCommand';
class CommanderApp { 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 this.program
.name('dojo') .name('dojo')
.description('CLI of the Dojo application') .description('CLI of the Dojo application')
...@@ -26,20 +46,37 @@ class CommanderApp { ...@@ -26,20 +46,37 @@ class CommanderApp {
sortSubcommands : true sortSubcommands : true
}) })
.option('-H, --host <string>', 'override the Dojo API endpoint', ClientsSharedConfig.apiURL) .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()) .addOption(new Option('--debug').hideHelp())
.hook('preAction', () => { .hook('preAction', (_thisCommand: Command, actionCommand: Command) => {
if ( this.hasToExecuteHook(actionCommand) ) {
this.warnDevelopmentVersion(); this.warnDevelopmentVersion();
}).hook('postAction', () => { }
})
.hook('postAction', (_thisCommand: Command, actionCommand: Command) => {
if ( this.hasToExecuteHook(actionCommand) ) {
this.informNewVersion(); 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', () => { this.program.on('option:host', () => {
ClientsSharedConfig.apiURL = this.program.opts().host; ClientsSharedConfig.apiURL = this.program.opts().host;
}); });
this.program.on('option:no_interactive', () => {
Config.interactiveMode = false;
});
this.program.on('option:interactive', () => { this.program.on('option:interactive', () => {
Config.interactiveMode = this.program.opts().interactive; Config.interactiveMode = true;
}); });
this.program.on('option:debug', () => { this.program.on('option:debug', () => {
...@@ -47,15 +84,17 @@ class CommanderApp { ...@@ -47,15 +84,17 @@ class CommanderApp {
}); });
this.registerCommands(); this.registerCommands();
}
public parse() {
this.program.parse(); this.program.parse();
} }
private warnDevelopmentVersion() { private warnDevelopmentVersion() {
if ( !SharedConfig.production ) { if ( !SharedConfig.production ) {
console.log(boxen(`This is a development (unstable) version of the DojoCLI. 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: If you want to use the stable version, please execute this command:
https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ${ TextStyle.CODE(' dojo upgrade ') }`, {
title : 'Warning', title : 'Warning',
titleAlignment: 'center', titleAlignment: 'center',
borderColor : 'red', borderColor : 'red',
...@@ -69,12 +108,12 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ...@@ -69,12 +108,12 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
private informNewVersion() { private informNewVersion() {
if ( SharedConfig.production ) { if ( SharedConfig.production ) {
const latestDojoCliVersion = stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0'; const latestDojoCliVersion = ConfigFiles.stateConfigFile.getParam('latestDojoCliVersion') as string | null || '0.0.0';
const latestDojoCliVersionNotification = stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0; const latestDojoCliVersionNotification = ConfigFiles.stateConfigFile.getParam('latestDojoCliVersionNotification') as number | null || 0;
if ( semver.lt(version, latestDojoCliVersion) ) { if ( semver.lt(version as string, latestDojoCliVersion) && (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) {
if ( (new Date()).getTime() - latestDojoCliVersionNotification >= Config.versionUpdateInformationPeriodHours * 60 * 60 * 1000 ) { console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available.
console.log(boxen(`The ${ latestDojoCliVersion } version of the DojoCLI is available: You can upgrade the DojoCLI by executing this command:
https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ${ TextStyle.CODE(' dojo upgrade ') }`, {
title : 'Information', title : 'Information',
titleAlignment: 'center', titleAlignment: 'center',
borderColor : 'blue', borderColor : 'blue',
...@@ -83,16 +122,20 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, { ...@@ -83,16 +122,20 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
padding : 1, padding : 1,
textAlignment : 'left' textAlignment : 'left'
})); }));
stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime()); ConfigFiles.stateConfigFile.setParam('latestDojoCliVersionNotification', (new Date()).getTime());
}
} }
} }
} }
private registerCommands() { private registerCommands() {
new AuthCommand().registerOnCommand(this.program);
SessionCommand.registerOnCommand(this.program); SessionCommand.registerOnCommand(this.program);
AssignmentCommand.registerOnCommand(this.program); AssignmentCommand.registerOnCommand(this.program);
ExerciseCommand.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 { abstract class CommanderCommand {
protected abstract commandName: string; protected abstract commandName: string;
protected aliasNames: Array<string> = [];
protected options: CommandOptions = {};
command: Command = new Command(); command: Command = new Command();
registerOnCommand(parent: Command) { registerOnCommand(parent: Command) {
this.command = parent.command(this.commandName); this.command = parent.command(this.commandName, this.options).aliases(this.aliasNames);
this.defineCommand(); this.defineCommand();
this.defineSubCommands(); this.defineSubCommands();
...@@ -14,7 +18,12 @@ abstract class CommanderCommand { ...@@ -14,7 +18,12 @@ abstract class CommanderCommand {
protected abstract defineCommand(): void; 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>; 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 CommanderCommand from '../CommanderCommand.js';
import AssignmentCreateCommand from './subcommands/AssignmentCreateCommand'; import AssignmentCreateCommand from './subcommands/AssignmentCreateCommand.js';
import AssignmentPublishCommand from './subcommands/AssignmentPublishCommand'; import AssignmentPublishCommand from './subcommands/AssignmentPublishCommand.js';
import AssignmentUnpublishCommand from './subcommands/AssignmentUnpublishCommand'; import AssignmentUnpublishCommand from './subcommands/AssignmentUnpublishCommand.js';
import AssignmentCheckCommand from './subcommands/AssignmentCheckCommand'; import AssignmentCheckCommand from './subcommands/AssignmentCheckCommand.js';
import AssignmentRunCommand from './subcommands/AssignmentRunCommand'; import AssignmentRunCommand from './subcommands/AssignmentRunCommand.js';
import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand'; import AssignmentCorrectionCommand from './subcommands/correction/AssignmentCorrectionCommand.js';
class AssignmentCommand extends CommanderCommand { class AssignmentCommand extends CommanderCommand {
...@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand { ...@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand {
AssignmentCorrectionCommand.registerOnCommand(this.command); AssignmentCorrectionCommand.registerOnCommand(this.command);
} }
protected async commandAction(): Promise<void> { } protected async commandAction(): Promise<void> {
// No action
}
} }
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand.js';
import Config from '../../../config/Config'; import Config from '../../../config/Config.js';
import ora from 'ora'; import ora from 'ora';
import chalk from 'chalk'; import chalk from 'chalk';
import AssignmentValidator from '../../../sharedByClients/helpers/Dojo/AssignmentValidator'; import AssignmentValidator from '../../../sharedByClients/helpers/Dojo/AssignmentValidator.js';
import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper'; import ClientsSharedAssignmentHelper from '../../../sharedByClients/helpers/Dojo/ClientsSharedAssignmentHelper.js';
import { Option } from 'commander'; import SharedConfig from '../../../shared/config/SharedConfig.js';
import SharedConfig from '../../../shared/config/SharedConfig'; import GlobalHelper from '../../../helpers/GlobalHelper.js';
class AssignmentCheckCommand extends CommanderCommand { class AssignmentCheckCommand extends CommanderCommand {
protected commandName: string = 'check'; protected commandName: string = 'check';
protected currentSpinner: ora.Ora = ora();
private verbose: boolean = false;
private superVerbose: boolean = false;
private buildPhase: boolean | undefined = undefined;
protected defineCommand() { protected defineCommand() {
this.command GlobalHelper.runCommandDefinition(this.command)
.description('locally run a check of an assignment') .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)); .action(this.commandAction.bind(this));
} }
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> { private logsEvent(log: string, _error: boolean, displayable: boolean, _currentStep: string, currentSubStep: string) {
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) => {
for ( const line of log.split('\n') ) { for ( const line of log.split('\n') ) {
if ( displayable && buildPhase == undefined && line.startsWith('#') ) { if ( displayable && this.buildPhase === undefined && line.startsWith('#') ) {
buildPhase = true; this.buildPhase = true;
} }
if ( currentSubStep == 'COMPOSE_RUN' && buildPhase === true && line != '' && !line.startsWith('#') ) { if ( currentSubStep === 'COMPOSE_RUN' && this.buildPhase === true && line !== '' && !line.startsWith('#') ) {
buildPhase = false; this.buildPhase = false;
} }
if ( SharedConfig.debug || (displayable && (options.superVerbose || buildPhase === false)) ) { if ( SharedConfig.debug || (displayable && (this.superVerbose || this.buildPhase === false)) ) {
console.log(line); console.log(line);
} }
} }
});
} }
assignmentValidator.events.on('step', (name: string, message: string) => { private subStepEvent(name: string, message: string) {
console.log(chalk.cyan(message)); this.currentSpinner = ora({
});
assignmentValidator.events.on('subStep', (name: string, message: string) => {
spinner = ora({
text : message, text : message,
indent: 4 indent: 4
}).start(); }).start();
if ( verbose && name == 'COMPOSE_RUN' ) { if ( this.verbose && name === 'COMPOSE_RUN' ) {
spinner.info(); this.currentSpinner.info();
}
} }
});
assignmentValidator.events.on('endSubStep', (stepName: string, message: string, error: boolean) => { private endSubStepEvent(stepName: string, message: string, error: boolean) {
if ( error ) { if ( error ) {
if ( verbose && stepName == 'COMPOSE_RUN' ) { if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({ ora({
text : message, text : message,
indent: 4 indent: 4
}).start().fail(); }).start().fail();
} else { } else {
spinner.fail(message); this.currentSpinner.fail(message);
} }
} else { } else {
if ( verbose && stepName == 'COMPOSE_RUN' ) { if ( this.verbose && stepName === 'COMPOSE_RUN' ) {
ora({ ora({
text : message, text : message,
indent: 4 indent: 4
}).start().succeed(); }).start().succeed();
} else { } else {
spinner.succeed(message); this.currentSpinner.succeed(message);
}
} }
} }
});
assignmentValidator.events.on('finished', (success: boolean) => { protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
success ? resolve() : reject(); 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 */ } } catch ( error ) { /* empty */ }
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand.js';
import chalk from 'chalk';
import ora from 'ora'; import ora from 'ora';
import AccessesHelper from '../../../helpers/AccessesHelper'; import SharedConfig from '../../../shared/config/SharedConfig';
import Assignment from '../../../sharedByClients/models/Assignment'; import AccessesHelper from '../../../helpers/AccessesHelper.js';
import GitlabUser from '../../../shared/types/Gitlab/GitlabUser'; import Assignment from '../../../sharedByClients/models/Assignment.js';
import GitlabManager from '../../../managers/GitlabManager'; import DojoBackendManager from '../../../managers/DojoBackendManager.js';
import DojoBackendManager from '../../../managers/DojoBackendManager'; import Toolbox from '../../../shared/helpers/Toolbox.js';
import Toolbox from '../../../shared/helpers/Toolbox'; 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 { class AssignmentCreateCommand extends CommanderCommand {
protected commandName: string = 'create'; 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() { protected defineCommand() {
this.command this.command
.description('create a new repository for an assignment') .description('create a new repository for an assignment')
.requiredOption('-n, --name <name>', 'name of the 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('-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('-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('-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)') .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)); .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> { private async dataRetrieval(options: CommandOptions) {
let members!: Array<GitlabUser> | false; console.log(TextStyle.BLOCK('Please wait while we verify and retrieve data...'));
let templateIdOrNamespace: string | null = null;
let assignment!: Assignment;
// Check access and retrieve data await AccessesHelper.checkTeachingStaff();
{
console.log(chalk.cyan('Please wait while we verify and retrieve data...'));
if ( !await AccessesHelper.checkTeachingStaff() ) { this.members = await Config.gitlabManager.fetchMembers(options);
return; if ( !this.members ) {
throw new Error();
} }
members = await GitlabManager.fetchMembers(options); if ( options.sonar ) {
if ( !members ) { const assignmentGetSonarSpinner: ora.Ora = ora('Checking server sonar status').start();
return; 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(); const assignmentGetSpinner: ora.Ora = ora('Checking assignment name availability').start();
if ( await DojoBackendManager.getAssignment(options.name) ) { if ( await DojoBackendManager.getAssignment(options.name) ) {
assignmentGetSpinner.fail(`Assignment name "${ options.name }" is already taken. Please choose another one.`); 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`); 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 ) { if ( options.template ) {
templateIdOrNamespace = options.template; this.templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(templateIdOrNamespace)) ) { if ( Number.isNaN(Number(this.templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string); this.templateIdOrNamespace = Toolbox.urlToPath(this.templateIdOrNamespace);
} }
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) { if ( !await DojoBackendManager.checkTemplateAccess(this.templateIdOrNamespace) ) {
return; throw new Error();
} }
} }
} }
// Create the assignment private async createAssignment(options: CommandOptions) {
{ console.log(TextStyle.BLOCK('Please wait while we are creating the assignment (approximately 10 seconds)...'));
console.log(chalk.cyan('Please wait while we are creating the assignment (approximately 10 seconds)...'));
try { this.assignment = await DojoBackendManager.createAssignment(options.name, options.language, this.members!, this.templateIdOrNamespace, this.sonar, !options.sonarStrict, this.sonarGate, this.sonarProfiles);
assignment = await DojoBackendManager.createAssignment(options.name, members, templateIdOrNamespace);
const oraInfo = (message: string) => { const oraInfo = (message: string) => {
ora({ ora({
...@@ -75,23 +136,29 @@ class AssignmentCreateCommand extends CommanderCommand { ...@@ -75,23 +136,29 @@ class AssignmentCreateCommand extends CommanderCommand {
}).start().info(); }).start().info();
}; };
oraInfo(`${ chalk.magenta('Name:') } ${ assignment.name }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Name:') } ${ this.assignment.name }`);
oraInfo(`${ chalk.magenta('Web URL:') } ${ assignment.gitlabCreationInfo.web_url }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('Web URL:') } ${ this.assignment.gitlabCreationInfo.web_url }`);
oraInfo(`${ chalk.magenta('HTTP Repo:') } ${ assignment.gitlabCreationInfo.http_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('HTTP Repo:') } ${ this.assignment.gitlabCreationInfo.http_url_to_repo }`);
oraInfo(`${ chalk.magenta('SSH Repo:') } ${ assignment.gitlabCreationInfo.ssh_url_to_repo }`); oraInfo(`${ TextStyle.LIST_ITEM_NAME('SSH Repo:') } ${ this.assignment.gitlabCreationInfo.ssh_url_to_repo }`);
} catch ( error ) { if ( this.assignment.useSonar ) {
return; 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 ) { 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 { class AssignmentPublishCommand extends AssignmentPublishUnpublishCommandBase {
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand.js';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import chalk from 'chalk'; import SessionManager from '../../../managers/SessionManager.js';
import SessionManager from '../../../managers/SessionManager';
import ora from 'ora'; import ora from 'ora';
import DojoBackendManager from '../../../managers/DojoBackendManager'; import DojoBackendManager from '../../../managers/DojoBackendManager.js';
import Assignment from '../../../sharedByClients/models/Assignment'; import Assignment from '../../../sharedByClients/models/Assignment.js';
import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper'; import SharedAssignmentHelper from '../../../shared/helpers/Dojo/SharedAssignmentHelper.js';
import TextStyle from '../../../types/TextStyle.js';
abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
...@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { ...@@ -35,7 +35,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand {
let assignment!: Assignment | undefined; 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) ) { if ( !await SessionManager.testSession(true, null) ) {
return; return;
...@@ -82,7 +82,7 @@ abstract class AssignmentPublishUnpublishCommandBase extends CommanderCommand { ...@@ -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 { try {
await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish); await DojoBackendManager.changeAssignmentPublishedStatus(assignment, this.publish);
......
import CommanderCommand from '../../CommanderCommand'; import CommanderCommand from '../../CommanderCommand.js';
import Config from '../../../config/Config'; import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper.js';
import { Option } from 'commander'; import GlobalHelper from '../../../helpers/GlobalHelper.js';
import ExerciseRunHelper from '../../../helpers/Dojo/ExerciseRunHelper';
class AssignmentRunCommand extends CommanderCommand { class AssignmentRunCommand extends CommanderCommand {
protected commandName: string = 'run'; protected commandName: string = 'run';
protected defineCommand() { protected defineCommand() {
// This command is synced with the "exercise run" command GlobalHelper.runCommandDefinition(this.command)
this.command
.description('locally run the assignment as an exercise') .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)); .action(this.commandAction.bind(this));
} }
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> { protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options); await (new ExerciseRunHelper(options)).run();
} }
} }
......