@learnpack/learnpack 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (103) hide show
  1. package/README.md +51 -398
  2. package/bin/run +14 -2
  3. package/oclif.manifest.json +1 -1
  4. package/package.json +135 -111
  5. package/src/commands/audit.ts +462 -0
  6. package/src/commands/clean.ts +29 -0
  7. package/src/commands/download.ts +62 -0
  8. package/src/commands/init.ts +169 -0
  9. package/src/commands/login.ts +42 -0
  10. package/src/commands/logout.ts +43 -0
  11. package/src/commands/publish.ts +107 -0
  12. package/src/commands/start.ts +229 -0
  13. package/src/commands/{test.js → test.ts} +19 -21
  14. package/src/index.ts +1 -0
  15. package/src/managers/config/allowed_files.ts +29 -0
  16. package/src/managers/config/defaults.ts +33 -0
  17. package/src/managers/config/exercise.ts +295 -0
  18. package/src/managers/config/index.ts +411 -0
  19. package/src/managers/file.ts +169 -0
  20. package/src/managers/gitpod.ts +84 -0
  21. package/src/managers/server/{index.js → index.ts} +26 -19
  22. package/src/managers/server/routes.ts +250 -0
  23. package/src/managers/session.ts +118 -0
  24. package/src/managers/socket.ts +239 -0
  25. package/src/managers/test.ts +83 -0
  26. package/src/models/action.ts +3 -0
  27. package/src/models/audit-errors.ts +4 -0
  28. package/src/models/config-manager.ts +23 -0
  29. package/src/models/config.ts +74 -0
  30. package/src/models/counter.ts +11 -0
  31. package/src/models/errors.ts +22 -0
  32. package/src/models/exercise-obj.ts +26 -0
  33. package/src/models/file.ts +5 -0
  34. package/src/models/findings.ts +18 -0
  35. package/src/models/flags.ts +10 -0
  36. package/src/models/front-matter.ts +11 -0
  37. package/src/models/gitpod-data.ts +19 -0
  38. package/src/models/language.ts +4 -0
  39. package/src/models/package.ts +7 -0
  40. package/src/models/plugin-config.ts +17 -0
  41. package/src/models/session.ts +26 -0
  42. package/src/models/socket.ts +48 -0
  43. package/src/models/status.ts +15 -0
  44. package/src/models/success-types.ts +1 -0
  45. package/src/plugin/command/compile.ts +17 -0
  46. package/src/plugin/command/test.ts +30 -0
  47. package/src/plugin/index.ts +6 -0
  48. package/src/plugin/plugin.ts +94 -0
  49. package/src/plugin/utils.ts +87 -0
  50. package/src/types/node-fetch.d.ts +1 -0
  51. package/src/ui/download.ts +71 -0
  52. package/src/utils/BaseCommand.ts +48 -0
  53. package/src/utils/SessionCommand.ts +48 -0
  54. package/src/utils/api.ts +194 -0
  55. package/src/utils/audit.ts +162 -0
  56. package/src/utils/console.ts +24 -0
  57. package/src/utils/errors.ts +117 -0
  58. package/src/utils/{exercisesQueue.js → exercisesQueue.ts} +12 -6
  59. package/src/utils/fileQueue.ts +198 -0
  60. package/src/utils/misc.ts +23 -0
  61. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +2 -4
  62. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +1 -2
  63. package/src/utils/templates/isolated/01-hello-world/README.es.md +1 -2
  64. package/src/utils/templates/isolated/01-hello-world/README.md +1 -2
  65. package/src/utils/templates/isolated/README.ejs +1 -1
  66. package/src/utils/templates/isolated/README.es.ejs +1 -1
  67. package/src/utils/validators.ts +18 -0
  68. package/src/utils/watcher.ts +27 -0
  69. package/plugin/command/compile.js +0 -17
  70. package/plugin/command/test.js +0 -29
  71. package/plugin/index.js +0 -6
  72. package/plugin/plugin.js +0 -71
  73. package/plugin/utils.js +0 -78
  74. package/src/commands/audit.js +0 -243
  75. package/src/commands/clean.js +0 -27
  76. package/src/commands/download.js +0 -52
  77. package/src/commands/hello.js +0 -20
  78. package/src/commands/init.js +0 -133
  79. package/src/commands/login.js +0 -45
  80. package/src/commands/logout.js +0 -39
  81. package/src/commands/publish.js +0 -78
  82. package/src/commands/start.js +0 -169
  83. package/src/index.js +0 -1
  84. package/src/managers/config/allowed_files.js +0 -12
  85. package/src/managers/config/defaults.js +0 -32
  86. package/src/managers/config/exercise.js +0 -212
  87. package/src/managers/config/index.js +0 -342
  88. package/src/managers/file.js +0 -137
  89. package/src/managers/server/routes.js +0 -151
  90. package/src/managers/session.js +0 -83
  91. package/src/managers/socket.js +0 -185
  92. package/src/managers/test.js +0 -77
  93. package/src/ui/download.js +0 -48
  94. package/src/utils/BaseCommand.js +0 -34
  95. package/src/utils/SessionCommand.js +0 -46
  96. package/src/utils/api.js +0 -164
  97. package/src/utils/audit.js +0 -114
  98. package/src/utils/console.js +0 -16
  99. package/src/utils/errors.js +0 -90
  100. package/src/utils/fileQueue.js +0 -194
  101. package/src/utils/misc.js +0 -26
  102. package/src/utils/validators.js +0 -15
  103. package/src/utils/watcher.js +0 -24
@@ -0,0 +1,83 @@
1
+ /*
2
+
3
+ import * as path from 'path'
4
+ import * as shell from 'shelljs'
5
+ import * as fs from 'fs'
6
+ import { TestingError } from './errors'
7
+ import Console from '../utils/console'
8
+ import * as color from 'colors'
9
+ import bcActivity from './bcActivity.js'
10
+ import { CompilerError } from '../utils/errors'
11
+ import { ISocket } from '../models/socket'
12
+ import { IFile } from '../models/file'
13
+ import { IConfig } from '@oclif/config'
14
+
15
+ module.exports = async function ({ socket, files, config, slug }: {socket: ISocket, files: IFile[], config: IConfig, slug: string}) {
16
+
17
+ const configPath = path.resolve(__dirname, `./config/tester/${config.tester}/${config.language}.config.js`);
18
+ if (!fs.existsSync(configPath)) throw CompilerError(`Uknown testing engine for compiler: '${config.language}'`);
19
+
20
+ const testingConfig = require(configPath)(files, config, slug);
21
+ testingConfig.validate();
22
+
23
+ if (config.ignoreTests) throw TestingError('Grading is disabled on learn.json file.');
24
+
25
+ if (!fs.existsSync(`${config.dirPath}/reports`)) {
26
+ fs.mkdirSync(`${config.dirPath}/reports`);
27
+ Console.debug(`Creating the ${config.dirPath}/reports directory`);
28
+ }
29
+
30
+ Console.info('Running tests...');
31
+
32
+ const command = await testingConfig.getCommand(socket)
33
+ const { stdout, stderr, code } = shell.exec(command);
34
+
35
+ if (code != 0) {
36
+ const errors = typeof (testingConfig.getErrors === 'function') ? testingConfig.getErrors(stdout || stderr) : [];
37
+ socket.log('testing-error', errors);
38
+ console.log(errors.join('\n'))
39
+
40
+ Console.error("There was an error while testing");
41
+ bcActivity.error('exercise_error', {
42
+ message: errors,
43
+ name: `${config.tester}-error`,
44
+ framework: config.tester,
45
+ language: config.language,
46
+ data: slug,
47
+ compiler: config.compiler
48
+ });
49
+ }
50
+ else {
51
+ socket.log('testing-success', [stdout || stderr].concat(["😁Everything is amazing!"]));
52
+ Console.success("Everything is amazing!");
53
+
54
+ bcActivity.activity('exercise_success', {
55
+ language: config.language,
56
+ slug: slug,
57
+ editor: config.editor,
58
+ compiler: config.compiler
59
+ });
60
+ config.exercises = config.exercises.map(e => {
61
+ if (e.slug === slug) e.done = true;
62
+ return e;
63
+ });
64
+ }
65
+
66
+ if (typeof testingConfig.cleanup !== "undefined") {
67
+ if (typeof testingConfig.cleanup === 'function' || typeof testingConfig.cleanup === 'object') {
68
+ const clean = await testingConfig.cleanup(socket);
69
+ if (clean) {
70
+ const { stdout, stderr, code } = shell.exec(clean);
71
+ if (code == 0) {
72
+ Console.debug("The cleanup command runned successfully");
73
+ }
74
+ else Console.warning("There is an error on the cleanup command for the test");
75
+ }
76
+
77
+ }
78
+ }
79
+
80
+ return true;
81
+ };
82
+
83
+ */
@@ -0,0 +1,3 @@
1
+ export type TAction = 'test' | 'log' | 'reload' | 'ready' | 'clean' | 'ask';
2
+
3
+ export type ICallback = (...agrs: any[]) => any;
@@ -0,0 +1,4 @@
1
+ export interface IAuditErrors {
2
+ exercise?: string;
3
+ msg: string;
4
+ }
@@ -0,0 +1,23 @@
1
+ import { IConfigObj, TGrading } from "./config";
2
+ import { IExercise } from "./exercise-obj";
3
+
4
+ export interface IConfigManagerAttributes {
5
+ grading: TGrading;
6
+ disableGrading: boolean;
7
+ version: string;
8
+ mode?: string;
9
+ }
10
+
11
+ export interface IConfigManager {
12
+ validLanguages?: any;
13
+ get: () => IConfigObj;
14
+ clean: () => void;
15
+ getExercise: (slug: string | undefined) => IExercise;
16
+ startExercise: (slug: string) => IExercise;
17
+ reset: (slug: string) => void;
18
+ buildIndex: () => boolean | void;
19
+ watchIndex: (onChange: (...args: Array<any>) => void) => void;
20
+ save: () => void;
21
+ noCurrentExercise: () => void;
22
+ getAllExercises: () => IExercise[];
23
+ }
@@ -0,0 +1,74 @@
1
+ import { IExercise } from "./exercise-obj";
2
+
3
+ export type TGrading = "isolated" | "incremental" | "no-grading";
4
+
5
+ export type TMode = "preview" | "standalone";
6
+
7
+ export type TConfigAction = "test" | "build" | "tutorial" | "reset";
8
+
9
+ export type TConfigObjAttributes = "config" | "exercises" | "grading";
10
+
11
+ export type TCompiler =
12
+ | "webpack"
13
+ | "vanillajs"
14
+ | "vue"
15
+ | "react"
16
+ | "css"
17
+ | "html";
18
+
19
+ export interface IConfigPath {
20
+ base: string;
21
+ }
22
+
23
+ export interface IEditor {
24
+ mode?: TMode;
25
+ version: string;
26
+ agent?: string;
27
+ }
28
+
29
+ export interface TEntries {
30
+ python3?: string;
31
+ html?: string;
32
+ node?: string;
33
+ react?: string;
34
+ java?: string;
35
+ }
36
+
37
+ export interface IConfig {
38
+ port?: string;
39
+ repository?: string;
40
+ description?: string;
41
+ slug?: string;
42
+ dirPath: string;
43
+ preview?: string; // Picture thumbnail
44
+ entries: TEntries;
45
+ grading: TGrading;
46
+ configPath: string;
47
+ translations: Array<string>;
48
+ outputPath?: string;
49
+ editor: IEditor;
50
+ language: string;
51
+ title: string;
52
+ duration: number;
53
+ difficulty?: string;
54
+ exercisesPath: string;
55
+ disableGrading: boolean; // TODO: Deprecate
56
+ actions: Array<string>; // TODO: Deprecate
57
+ // TODO: nameExerciseValidation
58
+ disabledActions?: Array<TConfigAction>;
59
+ compiler: TCompiler;
60
+ publicPath: string;
61
+ publicUrl?: string;
62
+ skills: Array<string>;
63
+ runHook: (...agrs: Array<any>) => void;
64
+ testingFinishedCallback: (arg: any | undefined) => void;
65
+ }
66
+
67
+ export interface IConfigObj {
68
+ session?: number;
69
+ currentExercise?: any;
70
+ config?: IConfig;
71
+ exercises?: Array<IExercise>;
72
+ confPath?: IConfigPath;
73
+ address?: string; // Maybe
74
+ }
@@ -0,0 +1,11 @@
1
+ interface ILinks {
2
+ error: number;
3
+ total: number;
4
+ }
5
+
6
+ export interface ICounter {
7
+ images: ILinks;
8
+ links: ILinks;
9
+ exercises: number;
10
+ readmeFiles: number;
11
+ }
@@ -0,0 +1,22 @@
1
+ export interface ISolution {
2
+ video?: string;
3
+ message: string;
4
+ slug?: string;
5
+ gif: string;
6
+ }
7
+
8
+ export interface IError extends TypeError {
9
+ status?: number;
10
+ type?:
11
+ | "validation-error"
12
+ | "not-found-error"
13
+ | "compiler-error"
14
+ | "testing-error"
15
+ | "auth-error"
16
+ | "internal-error";
17
+ slug?: string;
18
+ video?: string;
19
+ message: string;
20
+ gif?: string;
21
+ stdout?: string;
22
+ }
@@ -0,0 +1,26 @@
1
+ import { IFile } from "./file";
2
+ import { IConfig } from "./config";
3
+ import { ISocket } from "./socket";
4
+
5
+ export interface IExercise {
6
+ position?: number;
7
+ files: Array<IFile>;
8
+ slug: string;
9
+ path: string;
10
+ done: boolean;
11
+ language?: string | null;
12
+ entry?: string | null;
13
+ graded?: boolean;
14
+ translations?: { [key: string]: string };
15
+ title: string;
16
+ getReadme: (lang: string | null) => any;
17
+ getFile: (name: string) => string | Buffer;
18
+ saveFile: (name: string, content: string) => void;
19
+ getTestReport: () => any;
20
+ test?: (sessionConfig: any, config: IConfig, socket: ISocket) => void;
21
+ }
22
+
23
+ export interface IExerciseData {
24
+ exerciseSlug: string;
25
+ files: string[];
26
+ }
@@ -0,0 +1,5 @@
1
+ export interface IFile {
2
+ path: string;
3
+ name: string;
4
+ hidden: boolean;
5
+ }
@@ -0,0 +1,18 @@
1
+ interface IFindingOption {
2
+ content: string;
3
+ absUrl: string;
4
+ mdUrl: string;
5
+ relUrl: string;
6
+ }
7
+
8
+ interface ILinks {
9
+ [key: string]: IFindingOption;
10
+ }
11
+
12
+ export interface IFindings {
13
+ relativeImages?: ILinks;
14
+ externalImages?: ILinks;
15
+ markdownLinks?: ILinks;
16
+ url?: ILinks;
17
+ uploadcare?: ILinks;
18
+ }
@@ -0,0 +1,10 @@
1
+ export interface IStartFlags {
2
+ port?: string | unknown;
3
+ host?: string | unknown;
4
+ disableGrading?: boolean | unknown;
5
+ watch?: boolean | unknown;
6
+ editor?: string | unknown;
7
+ version?: string | unknown;
8
+ grading?: string | unknown;
9
+ debug?: boolean | unknown;
10
+ }
@@ -0,0 +1,11 @@
1
+ interface IAttributes {
2
+ intro: string;
3
+ tutorial: string;
4
+ }
5
+
6
+ export interface IFrontmatter {
7
+ attributes?: IAttributes;
8
+ body?: string;
9
+ bodyBegin?: number;
10
+ frontmatter?: string;
11
+ }
@@ -0,0 +1,19 @@
1
+ import {Server} from 'socket.io'
2
+ import {IConfigObj} from './config'
3
+
4
+ export type TFile = string;
5
+
6
+ export interface IGitpodData {
7
+ files: Array<TFile>;
8
+ }
9
+
10
+ export interface IGitpod {
11
+ socket: Server | null;
12
+ config: IConfigObj | null;
13
+ initialized: boolean;
14
+ hasGPCommand: boolean;
15
+ init: (config?: IConfigObj) => void;
16
+ openFiles: (files: Array<TFile>) => Promise<boolean | undefined>;
17
+ setup: (config?: IConfigObj) => void;
18
+ autosave: (value: string) => void;
19
+ }
@@ -0,0 +1,4 @@
1
+ export interface ILanguage {
2
+ title: string;
3
+ slug: string;
4
+ }
@@ -0,0 +1,7 @@
1
+ export interface IPackage {
2
+ title: string;
3
+ slug: string;
4
+ difficulty: string;
5
+ downloads: string;
6
+ skills: Array<any>;
7
+ }
@@ -0,0 +1,17 @@
1
+ import { IConfig } from "./config";
2
+ import { IExercise } from "./exercise-obj";
3
+
4
+ export interface IPluginConfig {
5
+ language: string;
6
+ compile?: {
7
+ run: () => void;
8
+ validate: (args: IValidate) => boolean;
9
+ dependencies: string[];
10
+ };
11
+ test?: () => void;
12
+ }
13
+
14
+ interface IValidate {
15
+ exercise: IExercise;
16
+ configuration: IConfig;
17
+ }
@@ -0,0 +1,26 @@
1
+ import {IConfig, IConfigObj} from './config'
2
+
3
+ export interface IPayload {
4
+ email: string;
5
+ }
6
+
7
+ export interface IStartProps {
8
+ token: string;
9
+ payload: IPayload | null;
10
+ }
11
+
12
+ export interface ISession {
13
+ sessionStarted: boolean;
14
+ token: string | null;
15
+ config: IConfig | null;
16
+ currentCohort: null;
17
+ initialize: () => Promise<boolean>;
18
+ setPayload: (value: IPayload) => Promise<boolean>;
19
+ getPayload: () => Promise<any>;
20
+ isActive: () => boolean;
21
+ get: (config?: IConfigObj) => Promise<any>;
22
+ login: () => Promise<void>;
23
+ sync: () => Promise<void>;
24
+ start: ({token, payload}: IStartProps) => Promise<void>;
25
+ destroy: () => Promise<void>;
26
+ }
@@ -0,0 +1,48 @@
1
+ import {IConfig} from './config'
2
+ import {Server} from 'socket.io'
3
+ import {DefaultEventsMap} from 'socket.io/dist/typed-events'
4
+ import {TAction, ICallback} from './action'
5
+ import {TStatus} from './status'
6
+
7
+ export type TPossibleActions = 'build' | 'test' | 'reset' | 'tutorial';
8
+
9
+ export interface ISocket {
10
+ socket: Server<
11
+ DefaultEventsMap,
12
+ DefaultEventsMap,
13
+ DefaultEventsMap,
14
+ any
15
+ > | null;
16
+ config: IConfig | null;
17
+ allowedActions: Array<TPossibleActions>;
18
+ actionCallBacks: { [key: string]: ICallback };
19
+ possibleActions: Array<TPossibleActions>;
20
+ isTestingEnvironment: boolean;
21
+ addAllowed: (actions: any) => void;
22
+ removeAllowed: (actions: any) => void;
23
+ start: (config: IConfig, server: any, isTestingEnvironment: boolean) => void;
24
+ on: (action: any, callBack: any) => void;
25
+ clean: (_: string, logs: Array<any>) => void;
26
+ ask: (questions: Array<string>) => void;
27
+ reload: (files: Array<string> | null, exercises: Array<string>) => void;
28
+ openWindow: (id: string) => void;
29
+ log: (
30
+ status: TStatus,
31
+ messages?: string | Array<string>,
32
+ report?: Array<string>,
33
+ data?: any
34
+ ) => void;
35
+ emit: (
36
+ action: TAction,
37
+ status: TStatus | string,
38
+ logs: string | Array<string>,
39
+ inputs?: Array<string>,
40
+ report?: Array<string>,
41
+ data?: any
42
+ ) => void;
43
+ ready: (message: string) => void;
44
+ error: (type: any, stdout: string) => void;
45
+ fatal: (msg: string) => void;
46
+ success: (type: any, stdout: string) => void;
47
+ onTestingFinished: (obj: any) => void;
48
+ }
@@ -0,0 +1,15 @@
1
+ export type TStatus =
2
+ | 'ready'
3
+ | 'internal-error'
4
+ | 'compiler-success'
5
+ | 'testing-success'
6
+ | 'compiling'
7
+ | 'testing'
8
+ | 'start_exercise'
9
+ | 'initializing'
10
+ | 'configuration_loaded'
11
+ | 'connection_ended'
12
+ | 'reset_exercise'
13
+ | 'open_files'
14
+ | 'open_window'
15
+ | 'instructions_closed';
@@ -0,0 +1 @@
1
+ export type TSuccessType = 'compiler-success' | 'testing-success';
@@ -0,0 +1,17 @@
1
+ import { IError } from "../../models/errors";
2
+
3
+ const CompilationError = (messages: string) => {
4
+ const _err: IError = new Error(messages);
5
+ _err.status = 400;
6
+ _err.stdout = messages;
7
+ _err.type = "compiler-error";
8
+ return _err;
9
+ };
10
+
11
+ export default {
12
+ CompilationError,
13
+ default: async ({ action, ...rest }: any) => {
14
+ const stdout = await action.run(rest);
15
+ return stdout;
16
+ },
17
+ };
@@ -0,0 +1,30 @@
1
+ import * as fs from "fs";
2
+ import { IError } from "../../models/errors";
3
+
4
+ const TestingError = (messages: string) => {
5
+ const _err: IError = new Error(messages);
6
+ _err.status = 400;
7
+ _err.stdout = messages;
8
+ _err.type = "testing-error";
9
+ return _err;
10
+ };
11
+
12
+ export default {
13
+ TestingError,
14
+ default: async function (args: any) {
15
+ const { action, configuration, socket, exercise } = args;
16
+
17
+ if (!fs.existsSync(`${configuration.dirPath}/reports`)) {
18
+ // reports directory
19
+ fs.mkdirSync(`${configuration.dirPath}/reports`);
20
+ }
21
+
22
+ // compile
23
+ const stdout = await action.run(args);
24
+
25
+ // mark exercise as done
26
+ exercise.done = true;
27
+
28
+ return stdout;
29
+ },
30
+ };
@@ -0,0 +1,6 @@
1
+ import CompilationError from "./command/compile";
2
+ import TestingError from "./command/test";
3
+ import utils from "./utils";
4
+ import plugin from "./plugin";
5
+
6
+ export default { CompilationError, TestingError, Utils: utils, plugin };
@@ -0,0 +1,94 @@
1
+ import * as shell from "shelljs";
2
+ import { IPluginConfig } from "../models/plugin-config";
3
+ /**
4
+ * Main Plugin Runner, it defines the behavior of a learnpack plugin
5
+ * dividing it in "actions" like: Compile, test, etc.
6
+ * @param {object} pluginConfig Configuration object that must defined language and each possible action.
7
+ */
8
+ export default (pluginConfig: IPluginConfig) => {
9
+ return async (args: any) => {
10
+ const { action, exercise, socket, configuration } = args;
11
+
12
+ if (pluginConfig.language === undefined)
13
+ throw new Error(`Missing language on the plugin configuration object`);
14
+
15
+ if (typeof action !== "string") {
16
+ throw new TypeError("Missing action property on hook details");
17
+ }
18
+
19
+ if (!exercise || exercise === undefined) {
20
+ throw new Error("Missing exercise information");
21
+ }
22
+
23
+ type actionType = "compile" | "test";
24
+
25
+ // if the action does not exist I don't do anything
26
+ if (pluginConfig[action as actionType] === undefined) {
27
+ console.log(`Ignoring ${action}`);
28
+ return () => null;
29
+ }
30
+
31
+ // ignore if the plugin language its not the same as the exercise language
32
+ if (exercise.language !== pluginConfig.language) {
33
+ return () => null;
34
+ }
35
+
36
+ if (!exercise.files || exercise.files.length === 0) {
37
+ throw new Error(`No files to process`);
38
+ }
39
+
40
+ try {
41
+ const _action = pluginConfig[action as actionType];
42
+
43
+ if (_action === null || typeof _action !== "object")
44
+ throw new Error(
45
+ `The ${pluginConfig.language} ${action} module must export an object configuration`
46
+ );
47
+ if (_action.validate === undefined)
48
+ throw new Error(
49
+ `Missing validate method for ${pluginConfig.language} ${action}`
50
+ );
51
+ if (_action.run === undefined)
52
+ throw new Error(
53
+ `Missing run method for ${pluginConfig.language} ${action}`
54
+ );
55
+ if (_action.dependencies !== undefined) {
56
+ if (!Array.isArray(_action.dependencies))
57
+ throw new Error(
58
+ `${action}.dependencies must be an array of package names`
59
+ );
60
+
61
+ for (const packageName of _action.dependencies) {
62
+ if (!shell.which(packageName)) {
63
+ throw new Error(
64
+ `🚫 You need to have ${packageName} installed to run test the exercises`
65
+ );
66
+ }
67
+ }
68
+ }
69
+
70
+ const valid = await _action.validate({ exercise, configuration });
71
+ if (valid) {
72
+ // look for the command standard implementation and execute it
73
+ const execute = require("./command/" + action + ".js").default;
74
+ // no matter the command, the response must always be a stdout
75
+ const stdout = await execute({
76
+ ...args,
77
+ action: _action,
78
+ configuration,
79
+ });
80
+
81
+ // Map the action names to socket messaging standards
82
+ const actionToSuccessMapper = { compile: "compiler", test: "testing" };
83
+
84
+ socket.success(actionToSuccessMapper[action as actionType], stdout);
85
+ return stdout;
86
+ }
87
+ } catch (error: any) {
88
+ if (error.type === undefined)
89
+ socket.fatal(error);
90
+ else
91
+ socket.error(error.type, error.stdout);
92
+ }
93
+ };
94
+ };