@learnpack/learnpack 1.0.0 → 2.0.3

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 +106 -118
  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 +172 -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 +234 -0
  13. package/src/commands/{test.js → test.ts} +21 -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 +302 -0
  18. package/src/managers/config/index.ts +412 -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 +255 -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,117 @@
1
+ import Console from './console'
2
+
3
+ import {ISolution, IError} from '../models/errors'
4
+
5
+ // eslint-disable-next-line
6
+ const fetch = require("node-fetch");
7
+
8
+ let solutions: { [key: string]: ISolution } | null = null
9
+
10
+ const uknown: ISolution = {
11
+ video: 'https://www.youtube.com/watch?v=gD1Sa99GiE4',
12
+ message: 'Uknown internal error',
13
+ slug: 'uknown',
14
+ gif: 'https://github.com/breatheco-de/breathecode-cli/blob/master/docs/errors/uknown.gif?raw=true',
15
+ }
16
+
17
+ export const getSolution = (slug?: string): ISolution => {
18
+ if (!slug) {
19
+ Console.debug('Getting solution templates from the learnpack repository')
20
+ } else {
21
+ Console.debug(`Getting solution for ${slug}`, solutions)
22
+ }
23
+
24
+ if (!solutions) {
25
+ Console.debug('Fetching for errors.json on github')
26
+ fetch(
27
+ 'https://raw.githubusercontent.com/breatheco-de/breathecode-cli/master/docs/errors/errors.json',
28
+ )
29
+ .then((r: Response) => r.json())
30
+ .then(function (_s: { [key: string]: ISolution }) {
31
+ solutions = _s
32
+ })
33
+ return uknown
34
+ }
35
+
36
+ return typeof solutions[slug || ''] === 'undefined' || !slug ?
37
+ uknown :
38
+ solutions[slug]
39
+ }
40
+
41
+ export const ValidationError = (error: IError | string) => {
42
+ const message: string = (error as IError).message || (error as string)
43
+ const _err = new Error(message) as IError
44
+ _err.status = 400
45
+ _err.type = 'validation-error'
46
+
47
+ const sol: ISolution = getSolution((error as IError).slug)
48
+ _err.video = sol.video
49
+ _err.gif = sol.gif
50
+ _err.message = typeof message === 'string' ? message : sol.message
51
+ return _err
52
+ }
53
+
54
+ export const NotFoundError = (error: IError | string) => {
55
+ const message = (error as IError).message || (error as string)
56
+ const _err = new Error(message) as IError
57
+ _err.status = 400
58
+ _err.type = 'not-found-error'
59
+
60
+ const sol = getSolution((error as IError).slug)
61
+ _err.video = sol.video
62
+ _err.gif = sol.gif
63
+ _err.message = typeof message === 'string' ? message : sol.message
64
+ return _err
65
+ }
66
+
67
+ export const CompilerError = (error: IError | string) => {
68
+ const message = (error as IError).message || (error as string)
69
+ const _err = new Error(message) as IError
70
+ _err.status = 400
71
+ _err.type = 'compiler-error'
72
+
73
+ const sol = getSolution((error as IError).slug)
74
+ _err.video = sol.video
75
+ _err.gif = sol.gif
76
+ _err.message = typeof message === 'string' ? message : sol.message
77
+ return _err
78
+ }
79
+
80
+ export const TestingError = (error: IError | string) => {
81
+ const message = (error as IError).message || (error as string)
82
+ const _err = new Error(message) as IError
83
+ _err.status = 400
84
+ _err.type = 'testing-error'
85
+ return _err
86
+ }
87
+
88
+ export const AuthError = (error: IError | string) => {
89
+ const message = (error as IError).message || (error as string)
90
+ const _err = new Error(message) as IError
91
+ _err.status = 403
92
+ _err.type = 'auth-error'
93
+ return _err
94
+ }
95
+
96
+ export const InternalError = (error: IError | string) => {
97
+ const message = (error as IError).message || (error as string)
98
+ const _err = new Error(message) as IError
99
+ _err.status = 500
100
+ _err.type = 'internal-error'
101
+
102
+ const sol = getSolution((error as IError).slug)
103
+ _err.video = sol.video
104
+ _err.gif = sol.gif
105
+ _err.message = typeof message === 'string' ? message : sol.message
106
+ return _err
107
+ }
108
+
109
+ getSolution()
110
+ export default {
111
+ ValidationError,
112
+ CompilerError,
113
+ TestingError,
114
+ NotFoundError,
115
+ InternalError,
116
+ AuthError,
117
+ }
@@ -1,9 +1,14 @@
1
+ import { IConfig } from "../models/config";
2
+ import { IExercise } from "../models/exercise-obj";
3
+ import { ISocket } from "../models/socket";
4
+
1
5
  class Exercise {
2
- constructor(exercise) {
6
+ exercise: IExercise;
7
+ constructor(exercise: IExercise) {
3
8
  this.exercise = exercise;
4
9
  }
5
10
 
6
- test(sessionConfig, config, socket) {
11
+ test(sessionConfig: IConfig, config: IConfig, socket: ISocket) {
7
12
  if (this.exercise.language) {
8
13
  socket.log(
9
14
  "testing",
@@ -17,14 +22,15 @@ class Exercise {
17
22
  exercise: this.exercise,
18
23
  });
19
24
  } else {
20
- socket.onTestingFinised({ result: "success" });
25
+ socket.onTestingFinished({ result: "success" });
21
26
  }
22
27
  }
23
28
  }
24
29
 
25
30
  class ExercisesQueue {
26
- constructor(exercises) {
27
- this.exercises = exercises.map((exercise) => {
31
+ exercises: IExercise[];
32
+ constructor(exercises: any) {
33
+ this.exercises = exercises.map((exercise: IExercise) => {
28
34
  return new Exercise(exercise);
29
35
  });
30
36
  }
@@ -42,4 +48,4 @@ class ExercisesQueue {
42
48
  }
43
49
  }
44
50
 
45
- module.exports = ExercisesQueue;
51
+ export default ExercisesQueue;
@@ -0,0 +1,198 @@
1
+ import logger from "../utils/console";
2
+ import * as fs from "fs";
3
+ // import em from "events"
4
+ import * as XXH from "xxhashjs";
5
+
6
+ // possible events to dispatch
7
+ const events = {
8
+ START_EXERCISE: "start_exercise",
9
+ INIT: "initializing",
10
+ RUNNING: "configuration_loaded",
11
+ END: "connection_ended",
12
+ RESET_EXERCISE: "reset_exercise",
13
+ OPEN_FILES: "open_files",
14
+ OPEN_WINDOW: "open_window",
15
+ INSTRUCTIONS_CLOSED: "instructions_closed",
16
+ };
17
+
18
+ let options = {
19
+ path: null,
20
+ create: false,
21
+ };
22
+ let lastHash: any = null;
23
+ let watcher: any = null; // subscribe to file and listen to changes
24
+ let actions: any = null; // action queue
25
+
26
+ const loadDispatcher = (opts: any) => {
27
+ actions = [{ name: "initializing", time: now() }];
28
+ logger.debug(`Loading from ${opts.path}`);
29
+
30
+ let exists = fs.existsSync(opts.path);
31
+ if (opts.create) {
32
+ if (exists)
33
+ actions.push({ name: "reset", time: now() });
34
+ fs.writeFileSync(opts.path, JSON.stringify(actions), { flag: "w" });
35
+ exists = true;
36
+ }
37
+
38
+ if (!exists)
39
+ throw new Error(`Invalid queue path, missing file at: ${opts.path}`);
40
+
41
+ let incomingActions = [];
42
+ try {
43
+ const content = fs.readFileSync(opts.path, "utf-8");
44
+ incomingActions = JSON.parse(content);
45
+ if (!Array.isArray(incomingActions))
46
+ incomingActions = [];
47
+ } catch {
48
+ incomingActions = [];
49
+ logger.debug("Error loading VSCode Actions file");
50
+ }
51
+
52
+ logger.debug("Actions load ", incomingActions);
53
+ return incomingActions;
54
+ };
55
+
56
+ // eslint-disable-next-line
57
+ const enqueue = (name: string, data: any | undefined = undefined) => {
58
+ if (!Object.values(events).includes(name)) {
59
+ logger.debug(`Invalid event ${name}`);
60
+ throw new Error(`Invalid action ${name}`);
61
+ }
62
+
63
+ if (!actions)
64
+ actions = [];
65
+
66
+ actions.push({ name, time: now(), data: data });
67
+ logger.debug(`EMIT -> ${name}:Exporting changes to ${options.path}`);
68
+
69
+ return fs.writeFileSync(options.path || "", JSON.stringify(actions));
70
+ };
71
+
72
+ const now = () => {
73
+ const hrTime = process.hrtime();
74
+ // eslint-disable-next-line
75
+ const htTime0 = hrTime[0] * 1000000;
76
+ return (htTime0 + hrTime[1]) / 1000;
77
+ };
78
+
79
+ const loadFile = (filePath: string) => {
80
+ if (!fs.existsSync(filePath))
81
+ throw new Error(`No queue.json file to load on ${filePath}`);
82
+
83
+ const content = fs.readFileSync(filePath, "utf8");
84
+ const newHash = XXH.h32(content, 0xAB_CD).toString(16);
85
+ const isUpdated = lastHash !== newHash;
86
+ lastHash = newHash;
87
+ const incomingActions = JSON.parse(content);
88
+ return { isUpdated, incomingActions };
89
+ };
90
+
91
+ const dequeue = () => {
92
+ // first time dequeue loads
93
+ if (!actions)
94
+ actions = [];
95
+
96
+ const { isUpdated, incomingActions } = loadFile(options.path || "");
97
+
98
+ if (!isUpdated) {
99
+ /**
100
+ * make sure no tasks are executed from the queue by matching both
101
+ * queues (the incoming with current one)
102
+ */
103
+ actions = incomingActions;
104
+ logger.debug(
105
+ `No new actions to process: ${actions.length}/${incomingActions.length}`
106
+ );
107
+ return null;
108
+ }
109
+
110
+ // do i need to reset actions to zero?
111
+ if (actions.length > 0 && actions[0].time !== incomingActions[0].time) {
112
+ actions = [];
113
+ }
114
+
115
+ const action = incomingActions[incomingActions.length - 1];
116
+ logger.debug("Dequeing action ", action);
117
+ actions.push(action);
118
+ return action;
119
+ };
120
+
121
+ const pull = (callback: (T: any) => any) => {
122
+ logger.debug("Pulling actions");
123
+ let incoming = dequeue();
124
+ while (incoming) {
125
+ callback(incoming);
126
+ incoming = dequeue();
127
+ }
128
+ };
129
+
130
+ const reset = (callback: (T?: any) => any) => {
131
+ logger.debug("Queue reseted");
132
+ actions = [];
133
+ if (fs.existsSync(options.path || "")) {
134
+ fs.writeFileSync(options.path || "", "[]");
135
+ callback();
136
+ }
137
+ };
138
+
139
+ const onPull = (callback: (T?: any) => any) => {
140
+ // eslint-disable-next-line
141
+ const chokidar = require("chokidar");
142
+
143
+ logger.debug("Starting to listen...");
144
+ try {
145
+ loadFile(options.path || "");
146
+ } catch {
147
+ logger.debug("No previeues queue file, waiting for it to be created...");
148
+ }
149
+
150
+ if (!watcher) {
151
+ logger.debug(`Watching ${options.path}`);
152
+ watcher = chokidar.watch(`${options.path}`, {
153
+ persistent: true,
154
+ });
155
+ } else
156
+ logger.debug("Already watching queue path");
157
+
158
+ watcher.on("add", () => pull(callback)).on("change", () => pull(callback));
159
+
160
+ return true;
161
+ };
162
+
163
+ const onReset = (callback: (T?: any) => any) => {
164
+ // eslint-disable-next-line
165
+ const chokidar = require("chokidar");
166
+
167
+ if (!watcher) {
168
+ logger.debug(`Watching ${options.path}`);
169
+ watcher = chokidar.watch(`${options.path}`, {
170
+ persistent: true,
171
+ });
172
+ }
173
+
174
+ watcher.on("unlink", () => reset(callback));
175
+
176
+ return true;
177
+ };
178
+
179
+ export default {
180
+ events,
181
+ dispatcher: (opts: any = {}) => {
182
+ if (!actions) {
183
+ options = { ...options, ...opts };
184
+ logger.debug("Initializing queue dispatcher", options);
185
+ actions = loadDispatcher(options);
186
+ }
187
+
188
+ return { enqueue, events };
189
+ },
190
+ listener: (opts: any = {}) => {
191
+ if (!actions) {
192
+ options = { ...options, ...opts };
193
+ logger.debug("Initializing queue listener", options);
194
+ }
195
+
196
+ return { onPull, onReset, events };
197
+ },
198
+ };
@@ -0,0 +1,23 @@
1
+ export const prioritizeHTMLFile = (entryFiles: string[]) => {
2
+ let files = []
3
+
4
+ // Find the html file and put it as latest in the files array
5
+ // in order to keep the html file opened in vscode plugin
6
+ const index = entryFiles.findIndex(file => {
7
+ return /.*\.html$/.test(file)
8
+ })
9
+
10
+ if (index !== -1) {
11
+ for (const [i, entryFile] of entryFiles.entries()) {
12
+ if (i !== index) {
13
+ files.push(entryFile)
14
+ }
15
+ }
16
+
17
+ files.push(entryFiles[index])
18
+ } else {
19
+ files = entryFiles
20
+ }
21
+
22
+ return files
23
+ }
@@ -1,10 +1,9 @@
1
- # `01` Hola Mundo
1
+ # `01` Hello World
2
2
 
3
3
  Puedes tener un archivo README el cual será como una página de un libro, sin archivos de código.
4
4
 
5
5
  También puedes agregar un archivo `README.[lenguaje].md` para traducciones, por ejemplo `README.es.md` para español.
6
6
 
7
-
8
7
  ## Inserta videos
9
8
 
10
9
  Si quieres incluir algún video introductorio para cada ejercicio, agrega la propiedad `intro` en el inicio del README.md para ese ejercicio en particular:
@@ -17,10 +16,9 @@ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
17
16
 
18
17
  Tambien puedes agregar un video explicando la solución para cada ejercicio agregando la propiedad `tutorial` al inicio del markdown del README.md correspondiente:
19
18
 
20
-
21
19
  ```markdown
22
20
  ---
23
21
  intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
22
  tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
23
  ---
26
- ```
24
+ ```
@@ -16,10 +16,9 @@ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
16
16
 
17
17
  You can also add a video solution for each exercise by adding a `tutorial` property on the markdown frontmatter of it's README.md:
18
18
 
19
-
20
19
  ```markdown
21
20
  ---
22
21
  intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
23
22
  tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
23
  ---
25
- ```
24
+ ```
@@ -18,10 +18,9 @@ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
18
18
 
19
19
  Tambien puedes agregar un video explicando la solución para cada ejercicio agregando la propiedad `tutorial` al inicio del markdown del README.md correspondiente:
20
20
 
21
-
22
21
  ```markdown
23
22
  ---
24
23
  intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
24
  tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
26
25
  ---
27
- ```
26
+ ```
@@ -18,10 +18,9 @@ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
18
18
 
19
19
  You can also add a video solution for each exercise by adding a `tutorial` property on the markdown frontmatter of it's README.md:
20
20
 
21
-
22
21
  ```markdown
23
22
  ---
24
23
  intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
24
  tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
26
25
  ---
27
- ```
26
+ ```
@@ -1,5 +1,5 @@
1
1
  # Welcome to <%= it.title %>
2
2
 
3
- You have initialized the exercises with grading=`isolated`, that basically means that your students will completed one exercise after the other with new files every time.
3
+ You have initialized the exercises with grading=`incremental`; meaning that your students will complete one single tutorial with clear and autograded steps from beginning to end.
4
4
 
5
5
  Type `$ learnpack start` in your terminal to start the exercises.
@@ -1,5 +1,5 @@
1
1
  # Bienvenido a <%= it.title %>
2
2
 
3
- Has inicializado los ejercicios con grading=`isolated`; lo que signigica que tus estudiantes completarán un ejercicio tras otro, en archivos nuevos cada vez.
3
+ Has inicializado los ejercicios con grading=`incremental`; esto signigica que tus estudiantes completarán un solo tutorial con pasos claros e incrementales, desde el comienzo hasta el final.
4
4
 
5
5
  Ejecuta `$ learnpack start` en la terminal para comenzar con los ejercicios.
@@ -0,0 +1,18 @@
1
+ export function validURL(str: string) {
2
+ if (!str.includes('github.com')) {
3
+ return false
4
+ }
5
+
6
+ const pattern = new RegExp(
7
+ '^(https?:\\/\\/)?' + // protocol
8
+ '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
9
+ '((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
10
+ '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
11
+ '(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
12
+ '(\\#[-a-z\\d_]*)?$',
13
+ 'i',
14
+ ) // fragment locator
15
+ return !!pattern.test(str)
16
+ }
17
+
18
+ export default {validURL}
@@ -0,0 +1,27 @@
1
+ import * as chokidar from "chokidar";
2
+ import * as debounce from "debounce";
3
+
4
+ export default (path: string) =>
5
+ new Promise((resolve /* , reject */) => {
6
+ const watcher = chokidar.watch(path, {
7
+ // TODO: This watcher is not ready yet
8
+ // ignored: (_path: string, _stats: any) => {
9
+ // return new RegExp(_path)
10
+ // },
11
+ persistent: true,
12
+ depth: 1,
13
+ ignoreInitial: true,
14
+ });
15
+
16
+ const onChange = (eventname: string, _filename: string) => {
17
+ resolve(eventname /* , filename */);
18
+ };
19
+
20
+ watcher.on("all", debounce(onChange, 500, true));
21
+ // watcher.on('all', onChange)
22
+
23
+ process.on("SIGINT", function () {
24
+ watcher.close();
25
+ process.exit();
26
+ });
27
+ });
@@ -1,17 +0,0 @@
1
-
2
- const CompilationError = (messages) => {
3
- const _err = new Error(messages)
4
- _err.status = 400
5
- _err.stdout = messages
6
- _err.type = 'compiler-error'
7
- return _err
8
- }
9
-
10
- module.exports = {
11
- CompilationError,
12
- default: async ({ action, ...rest }) => {
13
-
14
- const stdout = await action.run(rest)
15
- return stdout
16
- }
17
- }
@@ -1,29 +0,0 @@
1
- const fs = require('fs')
2
-
3
- const TestingError = (messages) => {
4
- const _err = new Error(messages)
5
- _err.status = 400
6
- _err.stdout = messages
7
- _err.type = 'testing-error'
8
- return _err
9
- }
10
-
11
- module.exports = {
12
- TestingError,
13
- default: async function(args){
14
- const { action, configuration, socket, exercise } = args;
15
-
16
- if (!fs.existsSync(`${configuration.dirPath}/reports`)){
17
- // reports directory
18
- fs.mkdirSync(`${configuration.dirPath}/reports`);
19
- }
20
-
21
- // compile
22
- const stdout = await action.run(args)
23
-
24
- // mark exercise as done
25
- exercise.done = true;
26
-
27
- return stdout
28
- }
29
- }
package/plugin/index.js DELETED
@@ -1,6 +0,0 @@
1
- const { CompilationError } = require("./command/compile")
2
- const { TestingError } = require("./command/test")
3
- const utils = require("./utils")
4
- const plugin = require("./plugin")
5
-
6
- module.exports = { CompilationError, TestingError, Utils: utils, plugin }
package/plugin/plugin.js DELETED
@@ -1,71 +0,0 @@
1
- const shell = require('shelljs')
2
- /**
3
- * Main Plugin Runner, it defines the behavior of a learnpack plugin
4
- * dividing it in "actions" like: Compile, test, etc.
5
- * @param {object} pluginConfig Configuration object that must defined language and each possible action.
6
- */
7
- module.exports = (pluginConfig) => {
8
- return async (args) => {
9
- const { action, exercise, socket, configuration } = args
10
-
11
-
12
- if(pluginConfig.language === undefined) throw Error(`Missing language on the plugin configuration object`)
13
-
14
- if(typeof action !== "string"){
15
- throw Error("Missing action property on hook details")
16
- }
17
-
18
- if(!exercise || exercise === undefined){
19
- throw Error("Missing exercise information")
20
- }
21
-
22
- // if the action does not exist I don't do anything
23
- if(pluginConfig[action] === undefined){
24
- console.log(`Ignoring ${action}`)
25
- return () => null
26
- }
27
-
28
- // ignore if the plugin language its not the same as the exercise language
29
- if(exercise.language !== pluginConfig.language){
30
- return () => null
31
- }
32
-
33
- if( !exercise.files || exercise.files.length == 0){
34
- throw Error(`No files to process`)
35
- }
36
-
37
- try{
38
- const _action = pluginConfig[action]
39
-
40
- if(_action == null || typeof _action != 'object') throw Error(`The ${pluginConfig.language} ${action} module must export an object configuration`)
41
- if(_action.validate === undefined) throw Error(`Missing validate method for ${pluginConfig.language} ${action}`)
42
- if(_action.run === undefined) throw Error(`Missing run method for ${pluginConfig.language} ${action}`)
43
- if(_action.dependencies !== undefined){
44
- if(!Array.isArray(_action.dependencies)) throw Error(`${action}.dependencies must be an array of package names`)
45
-
46
- _action.dependencies.forEach(packageName => {
47
- if (!shell.which(packageName)) {
48
- throw Error(`🚫 You need to have ${packageName} installed to run test the exercises`);
49
- }
50
- })
51
- }
52
- const valid = await _action.validate(({ exercise, configuration }))
53
- if(valid){
54
- // look for the command standard implementation and execute it
55
- const execute = require("./command/"+action+".js").default
56
- // no matter the command, the response must always be a stdout
57
- const stdout = await execute({ ...args, action: _action, configuration })
58
-
59
- // Map the action names to socket messaging standards
60
- const actionToSuccessMapper = { compile: 'compiler', test: 'testing' }
61
-
62
- socket.success(actionToSuccessMapper[action], stdout)
63
- return stdout
64
- }
65
- }
66
- catch(error){
67
- if(error.type == undefined) socket.fatal(error)
68
- else socket.error(error.type, error.stdout)
69
- }
70
- }
71
- }