Skip to content
Snippets Groups Projects
Commit 4db9ee9e authored by michael.minelli's avatar michael.minelli
Browse files

Sonar => Resolve issues

parent d50b7418
Branches
Tags
1 merge request!10Resolve "Add sonar integration"
Pipeline #30053 failed
Showing
with 231 additions and 218 deletions
......@@ -17,6 +17,7 @@
"commander": "^11.1.0",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
"form-data": "^4.0.0",
"fs-extra": "^11.2.0",
"http-status-codes": "^2.3.0",
"inquirer": "^8.2.6",
......
......@@ -41,6 +41,7 @@
"commander" : "^11.1.0",
"dotenv" : "^16.3.1",
"dotenv-expand" : "^10.0.0",
"form-data" : "^4.0.0",
"fs-extra" : "^11.2.0",
"http-status-codes" : "^2.3.0",
"inquirer" : "^8.2.6",
......@@ -51,6 +52,7 @@
"semver" : "^7.5.4",
"tar-stream" : "^3.1.6",
"winston" : "^3.11.0",
"winston-transport" : "^4.7.0",
"yaml" : "^2.3.4",
"zod" : "^3.22.4",
"zod-validation-error": "^3.0.0"
......
......@@ -20,4 +20,4 @@ import HttpManager from './managers/HttpManager';
HttpManager.registerAxiosInterceptor();
new CommanderApp();
\ No newline at end of file
(new CommanderApp()).parse();
\ No newline at end of file
......@@ -53,6 +53,10 @@ class CommanderApp {
this.program.parse();
}
public parse() {
this.program.parse();
}
private warnDevelopmentVersion() {
if ( !SharedConfig.production ) {
console.log(boxen(`This is a development (unstable) version of the DojoCLI.
......@@ -73,8 +77,7 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
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 ) {
if ( semver.lt(version, latestDojoCliVersion) && (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',
......@@ -89,7 +92,6 @@ https://gitedu.hesge.ch/dojo_project/projects/ui/dojocli/-/releases/Latest`, {
}
}
}
}
private registerCommands() {
new AuthCommand().registerOnCommand(this.program);
......
......@@ -18,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>;
}
......
......@@ -24,7 +24,9 @@ class AssignmentCommand extends CommanderCommand {
AssignmentCorrectionCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
......@@ -10,6 +10,10 @@ import GlobalHelper from '../../../helpers/GlobalHelper';
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() {
GlobalHelper.runCommandDefinition(this.command)
......@@ -17,78 +21,78 @@ class AssignmentCheckCommand extends CommanderCommand {
.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 */ }
......
......@@ -52,10 +52,10 @@ class AssignmentCreateCommand extends CommanderCommand {
templateIdOrNamespace = options.template;
if ( Number.isNaN(Number(templateIdOrNamespace)) ) {
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace as string);
templateIdOrNamespace = Toolbox.urlToPath(templateIdOrNamespace);
}
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace as string)) ) {
if ( !await DojoBackendManager.checkTemplateAccess(encodeURIComponent(templateIdOrNamespace)) ) {
return;
}
}
......
......@@ -13,7 +13,7 @@ class AssignmentRunCommand extends CommanderCommand {
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options);
await (new ExerciseRunHelper(options)).run();
}
}
......
......@@ -16,7 +16,9 @@ class AssignmentCorrectionCommand extends CommanderCommand {
AssignmentCorrectionUpdateCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
......@@ -18,7 +18,9 @@ class AuthCommand extends CommanderCommand {
SessionTestCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
......@@ -18,7 +18,9 @@ class CompletionCommand extends CommanderCommand {
CompletionZshCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
......@@ -11,7 +11,7 @@ import GlobalHelper from '../../../helpers
class CompletionFishCommand extends CommanderCommand {
protected commandName: string = 'fish';
private installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish');
private readonly installPath = path.join(os.homedir(), '.config/fish/completions/dojo.fish');
protected defineCommand() {
GlobalHelper.completionCommandDefinition(this.command)
......@@ -29,10 +29,11 @@ class CompletionFishCommand extends CommanderCommand {
spinner.succeed(`Fish completion successfully written in ${ filename }.`);
if ( showInstructions ) {
const cpCommand = ` cp -i ${ filename } ~/.config/fish/completions # interactive cp to avoid accidents `;
console.log(`
The easiest way to install the completion is to copy the ${ TextStyle.CODE(filename) } into the ${ TextStyle.CODE('~/.config/fish/completions') } directory.
${ TextStyle.CODE(` cp -i ${ filename } ~/.config/fish/completions # interactive cp to avoid accidents `) }`);
${ TextStyle.CODE(cpCommand) }`);
}
} catch ( error ) {
spinner.fail(`Fish completion error: ${ error }.`);
......
......@@ -37,6 +37,11 @@ source ${ this.bashCompletion }
spinner.succeed(`Bash completion successfully written in ${ TextStyle.CODE(filename) }`);
if ( showInstructions ) {
const zprofileContent = TextStyle.CODE(`
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`);
console.log(`
The easiest way to install the completion is to append the content of the generated file to the end of the ${ TextStyle.CODE(filename) } file or to overwrite it, if it only contains the 'dojo' completion.
......@@ -45,54 +50,40 @@ cat ${ filename } > ~/.bash_completion # overwrites .bash_completion
cat ${ filename } >> ~/.bash_completion # appends to .bash_completion`) }
For more details: ${ TextStyle.URL('https://github.com/scop/bash-completion/blob/master/README.md') }
Next add the following lines to your ${ TextStyle.CODE(`~/.zprofile`) } file: ${ TextStyle.CODE(`
autoload -U +X compinit && compinit
autoload -U +X bashcompinit && bashcompinit
source ${ filename }
`) } `);
Next add the following lines to your ${ TextStyle.CODE('~/.zprofile') } file: ${ zprofileContent } `);
}
} catch ( error ) {
spinner.fail(`Bash completion writing error: ${ error }`);
}
}
protected addToZprofile(path: string, bash_path: string) {
const spinner: ora.Ora = ora(`Modifying ${ path } ...`).start();
if ( fs.existsSync(path) ) {
const data = fs.readFileSync(path);
protected addToZprofile(zprofilePath: string, bashPath: string) {
const spinner: ora.Ora = ora(`Modifying ${ zprofilePath } ...`).start();
if ( fs.existsSync(zprofilePath) ) {
const data = fs.readFileSync(zprofilePath);
let updated = false;
if ( !data.includes('autoload -U +X compinit && compinit') ) {
try {
fs.appendFileSync(path, '\nautoload -U +X compinit && compinit');
if ( !data.includes('autoload -U +X compinit && compinit') ) {
fs.appendFileSync(zprofilePath, '\nautoload -U +X compinit && compinit');
updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
}
if ( !data.includes('autoload -U +X bashcompinit && bashcompinit') ) {
try {
fs.appendFileSync(path, '\nautoload -U +X bashcompinit && bashcompinit');
fs.appendFileSync(zprofilePath, '\nautoload -U +X bashcompinit && bashcompinit');
updated = true;
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
}
}
if ( !data.includes(`source ${ bash_path }`) ) {
try {
fs.appendFileSync(path, `\nsource ${ bash_path }`);
if ( !data.includes(`source ${ bashPath }`) ) {
fs.appendFileSync(zprofilePath, `\nsource ${ bashPath }`);
updated = true;
}
} catch {
spinner.fail(`Error appending in ${ this.zprofile }`);
return;
}
}
if ( updated ) {
spinner.succeed(`Zsh profile updated.`);
} else {
spinner.succeed(`Zsh profile already up to date.`);
}
spinner.succeed(updated ? `Zsh profile updated.` : `Zsh profile already up to date.`);
} else {
try {
fs.writeFileSync(path, this.loadBashCompletion);
fs.writeFileSync(zprofilePath, this.loadBashCompletion);
spinner.succeed(`Zsh profile written.`);
} catch ( error ) {
spinner.fail(`Error writing in ${ this.zprofile }`);
......
......@@ -18,7 +18,9 @@ class ExerciseCommand extends CommanderCommand {
ExerciseCorrectionCommand.registerOnCommand(this.command);
}
protected async commandAction(): Promise<void> { }
protected async commandAction(): Promise<void> {
// No action
}
}
......
......@@ -34,7 +34,6 @@ class ExerciseCorrectionCommand extends CommanderCommand {
Config.interactiveMode ? await this.showCorrectionsInteractive(assignment, assignmentGetSpinner) : this.showCorrections(assignment, assignmentGetSpinner);
} else {
assignmentGetSpinner.fail(`The assignment doesn't have any corrections yet`);
return;
}
}
......
......@@ -68,7 +68,7 @@ class ExerciseCreateCommand extends CommanderCommand {
console.log(TextStyle.BLOCK('Please wait while we are creating the exercise (approximately 10 seconds)...'));
try {
exercise = await DojoBackendManager.createExercise((assignment as Assignment).name, members);
exercise = await DojoBackendManager.createExercise(assignment.name, members);
const oraInfo = (message: string) => {
ora({
......
......@@ -13,7 +13,7 @@ class ExerciseRunCommand extends CommanderCommand {
}
protected async commandAction(options: { path: string, verbose: boolean, superVerbose: boolean }): Promise<void> {
await ExerciseRunHelper.run(options);
await (new ExerciseRunHelper(options)).run();
}
}
......
......@@ -4,7 +4,11 @@ import JSON5 from 'json5';
class LocalConfigFile {
constructor(private filename: string) {
private readonly filename: string;
constructor(filename: string) {
this.filename = filename;
this.loadConfig();
}
......
......@@ -4,15 +4,16 @@ import ora from 'ora';
import TextStyle from '../types/TextStyle';
import inquirer from 'inquirer';
function renameFile(filename: string, showWarning: boolean) {
const old_filename = `${filename}.old`
const spinner: ora.Ora = ora(`Renaming ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)} ...`).start();
const oldFilename = `${ filename }.old`;
const spinner: ora.Ora = ora(`Renaming ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) } ...`).start();
try {
renameSync(filename, old_filename);
spinner.succeed(`Renaming success: ${TextStyle.CODE(filename)} in ${TextStyle.CODE(old_filename)}`);
renameSync(filename, oldFilename);
spinner.succeed(`Renaming success: ${ TextStyle.CODE(filename) } in ${ TextStyle.CODE(oldFilename) }`);
if ( showWarning ) {
console.log(`${TextStyle.WARNING('Warning:')} Your ${TextStyle.CODE(filename)} was renamed ${TextStyle.CODE(old_filename)}. If this was not intended please revert this change.`);
console.log(`${ TextStyle.WARNING('Warning:') } Your ${ TextStyle.CODE(filename) } was renamed ${ TextStyle.CODE(oldFilename) }. If this was not intended please revert this change.`);
}
} catch ( error ) {
spinner.fail(`Renaming failed: ${ error }.`);
......@@ -25,24 +26,24 @@ async function askConfirmation(msg: string): Promise<boolean> {
message: msg,
type : 'confirm',
default: false
})).confirm
})).confirm;
}
// Returns false, when the renaming is interrupted
export async function tryRenameFile(path: string, force: boolean): Promise<boolean> {
const fileExists = existsSync(path)
const fileExists = existsSync(path);
if ( fileExists && force ) {
renameFile(path, false)
renameFile(path, false);
} else if ( fileExists ) {
const confirm = (await askConfirmation(`${TextStyle.CODE(path)} in ${TextStyle.CODE(path + '.old')}. Are you sure?`))
const confirm = (await askConfirmation(`${ TextStyle.CODE(path) } in ${ TextStyle.CODE(path + '.old') }. Are you sure?`));
if ( confirm ) {
renameFile(path, true)
renameFile(path, true);
} else {
console.log(`${TextStyle.BLOCK('Completion generation interrupted.')}`)
return false
console.log(`${ TextStyle.BLOCK('Completion generation interrupted.') }`);
return false;
}
}
return true
return true;
}
const fishFunction = `
......@@ -64,12 +65,12 @@ complete -f -c dojo
`;
function isHidden(cmd: Command): boolean {
return (cmd as Command & { _hidden: boolean })._hidden
return (cmd as Command & { _hidden: boolean })._hidden;
}
function isLeaf(cmd: Command): boolean {
return cmd.commands.length == 0;
return cmd.commands.length === 0;
}
function flatten(cmd: Command): Array<Command> {
......@@ -79,7 +80,7 @@ function flatten(cmd: Command): Array<Command> {
return cmd.commands
.filter(c => !isHidden(c))
.map(child => flatten(child))
.reduce((acc, cmd) => acc.concat(cmd), [cmd]);
.reduce((acc, subCmd) => acc.concat(subCmd), [ cmd ]);
}
}
......@@ -114,7 +115,7 @@ export function getRoot(cmd: Command): Command {
function getOptions(cmd: Command): string {
// we remove <args>, [command], and , from option lines
return cmd.options.filter(opt => !opt.hidden).map(opt => opt.flags.replace(/<.*?>/, '').replace(/\[.*?\]/, '').replace(',', '').trimEnd()).join(' ');
return cmd.options.filter(opt => !opt.hidden).map(opt => opt.flags.replace(/<.*?>/, '').replace(/\[.*?]/, '').replace(',', '').trimEnd()).join(' ');
}
function commandsAndOptionsToString(cmd: Command): string {
......@@ -126,9 +127,8 @@ function addLine(identLevel: number, pattern: string): string {
}
function generateBashSubCommands(cmd: Command, current: number, maxDepth: number, ident: number): string {
if (current == maxDepth) {
if ( current === maxDepth ) {
return addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`) + addLine(ident + 2, `words="${ commandsAndOptionsToString(cmd) }"`) + addLine(ident + 1, ';;') + addLine(ident + 1, '*)') + addLine(ident + 1, ';;') + addLine(ident, 'esac');
} else {
let data = addLine(ident, `case "\${COMP_WORDS[$COMP_CWORD - ${ maxDepth - current + 1 }]}" in`) + addLine(ident + 1, `${ cmd.name() })`);
cmd.commands.filter(c => !isHidden(c)).forEach(subCmd => {
......@@ -143,7 +143,7 @@ export function generateBashCompletion(root: Command): string {
const depth = computeDepth(root);
let data = addLine(0, '#/usr/bin/env bash\nfunction _dojo_completions()') + addLine(0, '{') + addLine(1, 'latest="${COMP_WORDS[$COMP_CWORD]}"');
for ( let i = 1 ; i <= depth ; i++ ) {
data += addLine(1, `${i == 1 ? 'if' : 'elif'} [ $COMP_CWORD -eq ${depth - i + 1} ]`) + addLine(1, 'then');
data += addLine(1, `${ i === 1 ? 'if' : 'elif' } [ $COMP_CWORD -eq ${ depth - i + 1 } ]`) + addLine(1, 'then');
data += generateBashSubCommands(root, i, depth, 2);
}
data += addLine(1, 'fi') + addLine(1, 'COMPREPLY=($(compgen -W "$words" -- $latest))') + addLine(1, 'return 0') + addLine(0, '}') + addLine(0, 'complete -F _dojo_completions dojo');
......@@ -167,21 +167,15 @@ function hasOptions(cmd: Command): boolean {
}
function optionsToString(cmd: Command): string {
return cmd.options.filter(opt => !opt.hidden).map(opt => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a "${opt.short ?? ''} ${opt.long ?? ''}" -d "${opt.description}"`;
}).join('\n').concat('\n');
return cmd.options.filter(opt => !opt.hidden).map(opt => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a "${ opt.short ?? '' } ${ opt.long ?? '' }" -d "${ opt.description }"`).join('\n').concat('\n');
}
export function generateFishCompletion(root: Command): string {
const commands = flatten(root);
const data = fishFunction.concat(// add completions for options
commands.filter(c => !isHidden(c)).filter(cmd => hasOptions(cmd)).map(cmd => optionsToString(cmd)).filter(str => str != '').join('')).concat(// add completions for commands and subcommands
commands.filter(c => !isHidden(c)).filter(cmd => !isLeaf(cmd)).map(cmd => cmd.commands.filter(c => !isHidden(c)).map(subCmd => {
return `${prefix} ${computeHeight(cmd)} ${generateCommandChain(cmd)}' -a ${subCmd.name()} -d "${subCmd.description()}"`;
}).join('\n').concat('\n')).join(''));
return data;
return fishFunction.concat(// add completions for options
commands.filter(c => !isHidden(c)).filter(cmd => hasOptions(cmd)).map(cmd => optionsToString(cmd)).filter(str => str !== '').join('')).concat(// add completions for commands and subcommands
commands.filter(c => !isHidden(c)).filter(cmd => !isLeaf(cmd)).map(cmd => cmd.commands.filter(c => !isHidden(c)).map(subCmd => `${ prefix } ${ computeHeight(cmd) } ${ generateCommandChain(cmd) }' -a ${ subCmd.name() } -d "${ subCmd.description() }"`).join('\n').concat('\n')).join(''));
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment