@learnpack/learnpack 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ };