@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
@@ -1,342 +0,0 @@
1
- const path = require("path");
2
- const fs = require("fs");
3
- const shell = require("shelljs");
4
- const Console = require("../../utils/console");
5
- const watch = require("../../utils/watcher");
6
- const chalk = require("chalk");
7
- const fetch = require("node-fetch");
8
- const {
9
- ValidationError,
10
- NotFoundError,
11
- InternalError,
12
- } = require("../../utils/errors.js");
13
-
14
- let defaults = require("./defaults.js");
15
- let { exercise } = require("./exercise.js");
16
-
17
- const { rmSync } = require("../file.js");
18
- /* exercise folder name standard */
19
-
20
- const getConfigPath = () => {
21
- const possibleFileNames = [
22
- "learn.json",
23
- ".learn/learn.json",
24
- "bc.json",
25
- ".breathecode/bc.json",
26
- ];
27
- let config = possibleFileNames.find((file) => fs.existsSync(file)) || null;
28
- if (config && fs.existsSync(".breathecode"))
29
- return { config, base: ".breathecode" };
30
- else if (config === null)
31
- throw NotFoundError(
32
- "learn.json file not found on current folder, is this a learnpack package?"
33
- );
34
- return { config, base: ".learn" };
35
- };
36
-
37
- const getExercisesPath = (base) => {
38
- const possibleFileNames = ["./exercises", base + "/exercises", "./"];
39
- return possibleFileNames.find((file) => fs.existsSync(file)) || null;
40
- };
41
-
42
- const getGitpodAddress = () => {
43
- if (shell.exec(`gp -h`, { silent: true }).code == 0) {
44
- return shell
45
- .exec(`gp url`, { silent: true })
46
- .stdout.replace(/(\r\n|\n|\r)/gm, "");
47
- } else {
48
- Console.debug(`Gitpod command line tool not found`);
49
- return "http://localhost";
50
- }
51
- };
52
-
53
- module.exports = async ({ grading, mode, disableGrading, version }) => {
54
- let confPath = getConfigPath();
55
- Console.debug("This is the config path: ", confPath);
56
-
57
- let configObj = {};
58
- if (confPath) {
59
- const bcContent = fs.readFileSync(confPath.config);
60
-
61
- let hiddenBcContent = {};
62
- if (fs.existsSync(confPath.base + "/config.json")) {
63
- hiddenBcContent = fs.readFileSync(confPath.base + "/config.json");
64
- hiddenBcContent = JSON.parse(hiddenBcContent);
65
- if (!hiddenBcContent)
66
- throw Error(
67
- `Invalid ${confPath.base}/config.json syntax: Unable to parse.`
68
- );
69
- }
70
-
71
- const jsonConfig = JSON.parse(bcContent);
72
- if (!jsonConfig)
73
- throw Error(`Invalid ${confPath.config} syntax: Unable to parse.`);
74
-
75
- //add using id to the installation
76
- if (!jsonConfig.session)
77
- jsonConfig.session = Math.floor(Math.random() * 10000000000000000000);
78
-
79
- configObj = deepMerge(hiddenBcContent, { config: jsonConfig }, {
80
- config: { disableGrading },
81
- });
82
- Console.debug("Content form the configuration .json ", configObj);
83
- } else {
84
- throw ValidationError(
85
- "No learn.json file has been found, make sure you are in the folder"
86
- );
87
- }
88
-
89
- configObj = deepMerge(defaults || {}, configObj, {
90
- config: {
91
- grading: grading || configObj.grading,
92
- configPath: confPath.config,
93
- },
94
- });
95
- configObj.config.outputPath = confPath.base + "/dist";
96
-
97
- Console.debug("This is your configuration object: ", {
98
- ...configObj,
99
- exercises: configObj.exercises
100
- ? configObj.exercises.map((e) => e.slug)
101
- : [],
102
- });
103
-
104
- // auto detect agent (if possible)
105
- if (shell.which("gp")) {
106
- configObj.config.editor.agent = "gitpod";
107
- configObj.config.address = getGitpodAddress();
108
- configObj.config.publicUrl = `https://${
109
- configObj.config.port
110
- }-${configObj.config.address.substring(8)}`;
111
- } else if (!configObj.config.editor.agent) {
112
- configObj.config.editor.agent = "localhost";
113
- }
114
-
115
- if (!configObj.config.publicUrl)
116
- configObj.config.publicUrl = `${configObj.config.address}:${configObj.config.port}`;
117
-
118
- // Assign default editor mode if not set already
119
- if (mode != null) {
120
- configObj.config.editor.mode = mode;
121
- }
122
-
123
- if (!configObj.config.mode)
124
- configObj.config.editor.mode =
125
- configObj.config.editor.agent === "localhost" ? "standalone" : "preview";
126
-
127
- if (version) configObj.config.editor.version = version;
128
- else if (configObj.config.editor.version === null) {
129
- const resp = await fetch(
130
- "https://raw.githubusercontent.com/learnpack/coding-ide/learnpack/package.json"
131
- );
132
- const packageJSON = await resp.json();
133
- configObj.config.editor.version = packageJSON.version || "1.0.61";
134
- }
135
-
136
- configObj.config.dirPath = "./" + confPath.base;
137
- configObj.config.exercisesPath = getExercisesPath(confPath.base) || "./";
138
-
139
- return {
140
- validLanguages: {},
141
- get: () => configObj,
142
- validateEngine: function (language, server, socket) {
143
- const alias = (_l) => {
144
- let map = {
145
- python3: "python",
146
- };
147
- if (map[_l]) return map[_l];
148
- else return _l;
149
- };
150
-
151
- // decode aliases
152
- language = alias(language);
153
-
154
- if (this.validLanguages[language]) return true;
155
-
156
- Console.debug(`Validating engine for ${language} compilation`);
157
- let result = shell.exec("learnpack plugins", { silent: true });
158
-
159
- if (result.code == 0 && result.stdout.includes(`learnpack-${language}`)) {
160
- this.validLanguages[language] = true;
161
- return true;
162
- }
163
-
164
- Console.info(`Language engine for ${language} not found, installing...`);
165
- result = shell.exec(`learnpack plugins:install learnpack-${language}`, {
166
- silent: true,
167
- });
168
- if (result.code === 0) {
169
- socket.log(
170
- "compiling",
171
- "Installing the python compiler, you will have to reset the exercises after installation by writing on your terminal: $ learnpack run"
172
- );
173
- Console.info(
174
- `Successfully installed the ${language} exercise engine, \n please start learnpack again by running the following command: \n ${chalk.white(
175
- `$ learnpack start`
176
- )}\n\n `
177
- );
178
- server.terminate();
179
- return false;
180
- } else {
181
- this.validLanguages[language] = false;
182
- socket.error(`Error installing ${language} exercise engine`);
183
- Console.error(`Error installing ${language} exercise engine`);
184
- Console.log(result.stdout);
185
- throw InternalError(`Error installing ${language} exercise engine`);
186
- }
187
- },
188
- clean: () => {
189
- rmSync(configObj.config.outputPath);
190
- rmSync(configObj.config.dirPath + "/_app");
191
- rmSync(configObj.config.dirPath + "/reports");
192
- rmSync(configObj.config.dirPath + "/.session");
193
- rmSync(configObj.config.dirPath + "/resets");
194
-
195
- // clean tag gz
196
- if (fs.existsSync(configObj.config.dirPath + "/app.tar.gz"))
197
- fs.unlinkSync(configObj.config.dirPath + "/app.tar.gz");
198
-
199
- if (fs.existsSync(configObj.config.dirPath + "/config.json"))
200
- fs.unlinkSync(configObj.config.dirPath + "/config.json");
201
-
202
- if (fs.existsSync(configObj.config.dirPath + "/vscode_queue.json"))
203
- fs.unlinkSync(configObj.config.dirPath + "/vscode_queue.json");
204
- },
205
- getExercise: (slug) => {
206
- const exercise = configObj.exercises.find((ex) => ex.slug == slug);
207
- if (!exercise) throw ValidationError(`Exercise ${slug} not found`);
208
-
209
- return exercise;
210
- },
211
- getAllExercises: () => {
212
- return configObj.exercises;
213
- },
214
- startExercise: function (slug) {
215
- const exercise = this.getExercise(slug);
216
-
217
- // set config.json with current exercise
218
- configObj.currentExercise = exercise.slug;
219
-
220
- this.save();
221
-
222
- exercise.files.forEach((f) => {
223
- const _path = configObj.config.outputPath + "/" + f.name;
224
- if (f.hidden === false && fs.existsSync(_path)) fs.unlinkSync(_path);
225
- });
226
-
227
- return exercise;
228
- },
229
- noCurrentExercise: function () {
230
- configObj.currentExercise = null;
231
- this.save();
232
- },
233
- reset: (slug) => {
234
- if (!fs.existsSync(`${configObj.config.dirPath}/resets/` + slug))
235
- throw ValidationError("Could not find the original files for " + slug);
236
-
237
- const exercise = configObj.exercises.find((ex) => ex.slug == slug);
238
- if (!exercise)
239
- throw ValidationError(
240
- `Exercise ${slug} not found on the configuration`
241
- );
242
-
243
- fs.readdirSync(`${configObj.config.dirPath}/resets/${slug}/`).forEach(
244
- (fileName) => {
245
- const content = fs.readFileSync(
246
- `${configObj.config.dirPath}/resets/${slug}/${fileName}`
247
- );
248
- fs.writeFileSync(`${exercise.path}/${fileName}`, content);
249
- }
250
- );
251
- },
252
- buildIndex: function () {
253
- Console.info("Building the exercise index...");
254
-
255
- const isDirectory = (source) => {
256
- const name = path.basename(source);
257
- if (name === path.basename(configObj.config.dirPath)) return false;
258
- //ignore folders that start with a dot
259
- if (name.charAt(0) === "." || name.charAt(0) === "_") return false;
260
-
261
- return fs.lstatSync(source).isDirectory();
262
- };
263
- const getDirectories = (source) =>
264
- fs
265
- .readdirSync(source)
266
- .map((name) => path.join(source, name))
267
- .filter(isDirectory);
268
- // add the .learn folder
269
- if (!fs.existsSync(confPath.base)) fs.mkdirSync(confPath.base);
270
- // add the outout folder where webpack will publish the the html/css/js files
271
- if (
272
- configObj.config.outputPath &&
273
- !fs.existsSync(configObj.config.outputPath)
274
- )
275
- fs.mkdirSync(configObj.config.outputPath);
276
-
277
- // TODO: we could use npm library front-mater to read the title of the exercises from the README.md
278
- const grupedByDirectory = getDirectories(configObj.config.exercisesPath);
279
- if (grupedByDirectory.length > 0)
280
- configObj.exercises = grupedByDirectory.map((path, position) =>
281
- exercise(path, position, configObj)
282
- );
283
- // else means the exercises are not in a folder
284
- else
285
- configObj.exercises = [
286
- exercise(configObj.config.exercisesPath, 0, configObj),
287
- ];
288
- this.save();
289
- },
290
- watchIndex: function (onChange = null) {
291
- if (!configObj.config.exercisesPath)
292
- throw ValidationError(
293
- "No exercises directory to watch: " + configObj.config.exercisesPath
294
- );
295
-
296
- this.buildIndex();
297
- watch(configObj.config.exercisesPath)
298
- .then((eventname, filename) => {
299
- Console.debug("Changes detected on your exercises");
300
- this.buildIndex();
301
- if (onChange) onChange();
302
- })
303
- .catch((error) => {
304
- throw error;
305
- });
306
- },
307
- save: (config = null) => {
308
- Console.debug("Saving configuration with: ", configObj);
309
-
310
- //remove the duplicates form the actions array
311
- // configObj.config.actions = [...new Set(configObj.config.actions)];
312
- configObj.config.translations = [
313
- ...new Set(configObj.config.translations),
314
- ];
315
-
316
- fs.writeFileSync(
317
- configObj.config.dirPath + "/config.json",
318
- JSON.stringify(configObj, null, 4)
319
- );
320
- },
321
- };
322
- };
323
-
324
- function deepMerge(...sources) {
325
- let acc = {};
326
- for (const source of sources) {
327
- if (source instanceof Array) {
328
- if (!(acc instanceof Array)) {
329
- acc = [];
330
- }
331
- acc = [...source];
332
- } else if (source instanceof Object) {
333
- for (let [key, value] of Object.entries(source)) {
334
- if (value instanceof Object && key in acc) {
335
- value = deepMerge(acc[key], value);
336
- }
337
- if (value !== undefined) acc = { ...acc, [key]: value };
338
- }
339
- }
340
- }
341
- return acc;
342
- }
@@ -1,137 +0,0 @@
1
- var fs = require('fs')
2
- var p = require('path')
3
- let shell = require('shelljs')
4
- const {cli} = require('cli-ux')
5
- var targz = require('targz')
6
- let Console = require('../utils/console')
7
- var https = require('https')
8
- var fetch = require('node-fetch')
9
- const { InternalError } = require('../utils/errors');
10
-
11
- const decompress = (sourcePath, destinationPath) => new Promise((resolve, reject) => {
12
- Console.debug("Decompressing "+sourcePath)
13
- targz.decompress({
14
- src: sourcePath,
15
- dest: destinationPath
16
- }, function(err){
17
- if(err) {
18
- Console.error("Error when trying to decompress")
19
- reject(err)
20
- } else {
21
- Console.info("Decompression finished successfully")
22
- resolve()
23
- }
24
- })
25
- })
26
-
27
- const downloadEditor = async (version, destination) => {
28
- //https://raw.githubusercontent.com/learnpack/coding-ide/master/dist/app.tar.gz
29
- //if(versions[version] === undefined) throw new Error(`Invalid editor version ${version}`)
30
- const resp2 = await fetch(`https://github.com/learnpack/coding-ide/blob/${version}/dist`)
31
- if(!resp2.ok) throw InternalError(`Coding Editor v${version} was not found on learnpack repository, check the config.editor.version property on learn.json`)
32
-
33
- Console.info(`Downloading the LearnPack coding UI v${version}, this may take a minute...`)
34
- return await download(`https://github.com/learnpack/coding-ide/blob/${version}/dist/app.tar.gz?raw=true`, destination)
35
- }
36
-
37
- const download = (url, dest) =>{
38
- Console.debug("Downloading "+url)
39
- return new Promise((resolve, reject) => {
40
- const request = https.get(url, response => {
41
- if (response.statusCode === 200) {
42
- const file = fs.createWriteStream(dest, { flags: 'wx' })
43
- file.on('finish', () => {
44
- resolve(true)
45
- })
46
- file.on('error', err => {
47
- file.close()
48
- if (err.code === 'EEXIST'){
49
- Console.debug("File already exists")
50
- resolve("File already exists")
51
- }
52
- else{
53
- Console.debug("Error ",err.message)
54
- fs.unlink(dest, () => reject(err.message)) // Delete temp file
55
- }
56
-
57
- })
58
- response.pipe(file)
59
- } else if (response.statusCode === 302 || response.statusCode === 301) {
60
- //Console.debug("Servers redirected to "+response.headers.location)
61
- //Recursively follow redirects, only a 200 will resolve.
62
- download(response.headers.location, dest)
63
- .then(() => resolve())
64
- .catch(error => {
65
- Console.error(error)
66
- reject(error)
67
- })
68
- } else {
69
- Console.debug(`Server responded with ${response.statusCode}: ${response.statusMessage}`)
70
- reject(`Server responded with ${response.statusCode}: ${response.statusMessage}`)
71
- }
72
- })
73
-
74
- request.on('error', err => {
75
- reject(err.message)
76
- })
77
- })
78
- }
79
-
80
- const clone = (repository=null, folder='./') => new Promise((resolve, reject)=>{
81
-
82
- if(!repository){
83
- reject("Missing repository url for this package")
84
- return false
85
- }
86
-
87
- cli.action.start('Verifying GIT...')
88
- if (!shell.which('git')) {
89
- reject('Sorry, this script requires git')
90
- return false
91
- }
92
- cli.action.stop()
93
-
94
- let fileName = p.basename(repository)
95
- if(!fileName){
96
- reject('Invalid repository information on package: '+repository)
97
- return false
98
- }
99
-
100
- fileName = fileName.split('.')[0];
101
- if(fs.existsSync("./"+fileName)){
102
- reject(`Directory ${fileName} already exists; Did you download this package already?`)
103
- return false
104
- }
105
-
106
- cli.action.start(`Cloning repository ${repository}...`)
107
- if (shell.exec(`git clone ${repository}`).code !== 0) {
108
- reject('Error: Installation failed')
109
- }
110
- cli.action.stop()
111
-
112
- cli.action.start('Cleaning installation...')
113
- if (shell.exec(`rm -R -f ${folder}${fileName}/.git`).code !== 0) {
114
- reject('Error: removing .git directory')
115
- }
116
- cli.action.stop()
117
-
118
- resolve("Done")
119
- })
120
-
121
- const rmSync = function(path) {
122
- var files = [];
123
- if( fs.existsSync(path) ) {
124
- files = fs.readdirSync(path);
125
- files.forEach(function(file,index){
126
- var curPath = path + "/" + file;
127
- if(fs.lstatSync(curPath).isDirectory()) { // recurse
128
- rmSync(curPath);
129
- } else { // delete file
130
- fs.unlinkSync(curPath);
131
- }
132
- });
133
- fs.rmdirSync(path);
134
- }
135
- };
136
-
137
- module.exports = { download, decompress, downloadEditor, clone, rmSync }
@@ -1,151 +0,0 @@
1
- const Console = require('../../utils/console')
2
- const express = require('express')
3
- const fs = require('fs')
4
- const bodyParser = require('body-parser')
5
- const socket = require('../socket.js');
6
- const queue = require("../../utils/fileQueue")
7
- const { detect, filterFiles } = require("../config/exercise");
8
-
9
- const withHandler = (func) => (req, res) => {
10
- try{
11
- func(req, res)
12
- }
13
- catch(err){
14
- Console.debug(err)
15
- const _err = {
16
- message: err.message || 'There has been an error' ,
17
- status: err.status || 500,
18
- type: err.type || null
19
- }
20
- Console.error(_err.message)
21
-
22
- //send rep to the server
23
- res.status(_err.status)
24
- res.json(_err)
25
- }
26
- }
27
-
28
- module.exports = async function(app, configObject, configManager){
29
-
30
- const { config } = configObject;
31
-
32
- // Dispatcher will communicate events to 3rd party apps that
33
- // subscribe to the queue, you can read documentation about this queue here:
34
- // https://github.com/alesanchezr/text-queue/blob/main/README.md
35
- const dispatcher = queue.dispatcher({ create: true, path: `${config.dirPath}/vscode_queue.json` })
36
-
37
- app.get('/config', withHandler((req, res)=>{
38
- res.json(configObject)
39
- }))
40
-
41
- /**
42
- * TODO: replicate a socket action, the request payload must be passed to the socket as well
43
- */
44
- const jsonBodyParser = bodyParser.json()
45
- app.post('/socket/:actionName', jsonBodyParser, withHandler((req, res)=>{
46
- if(socket[req.params.actionName] instanceof Function){
47
- socket[req.params.actionName](req.body ? req.body.data : null)
48
- res.json({ "details": "Socket call executed sucessfully"})
49
- }else res.status(400).json({ "details": `Socket action ${req.params.actionName} not found`})
50
- }))
51
-
52
- //symbolic link to maintain path compatiblity
53
- const fetchStaticAsset = withHandler((req, res) => {
54
- let filePath = `${config.dirPath}/assets/${req.params.filePath}`
55
- if (!fs.existsSync(filePath)) throw Error('File not found: '+filePath)
56
- const content = fs.readFileSync(filePath)
57
- res.write(content);
58
- res.end();
59
- })
60
- app.get(`${config.dirPath.indexOf("./") === 0 ? config.dirPath.substring(1) : config.dirPath}/assets/:filePath`, fetchStaticAsset);
61
- app.get('/assets/:filePath', fetchStaticAsset);
62
-
63
- app.get('/exercise', withHandler((req, res) => {
64
- res.json(configObject.exercises)
65
- }))
66
-
67
- app.get('/exercise/:slug/readme', withHandler((req, res) => {
68
- const readme = configManager.getExercise(req.params.slug).getReadme(req.query.lang || null)
69
- res.json(readme)
70
- }))
71
-
72
- app.get('/exercise/:slug/report', withHandler((req, res) => {
73
- const report = configManager.getExercise(req.params.slug).getTestReport()
74
- res.json(JSON.stringify(report))
75
- }))
76
-
77
- app.get('/exercise/:slug', withHandler((req, res) => {
78
-
79
- // no need to re-start exercise if it's already started
80
- if(configObject.currentExercise && req.params.slug === configObject.currentExercise){
81
- let exercise = configManager.getExercise(req.params.slug)
82
- res.json(exercise)
83
- return;
84
- }
85
-
86
- let exercise = configManager.startExercise(req.params.slug)
87
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
88
-
89
- const entries = Object.keys(config.entries).map(lang => config.entries[lang]);
90
-
91
- // if we are in incremental grading, the entry file can by dinamically detected
92
- // based on the changes the student is making during the exercise
93
- if(config.grading === "incremental"){
94
- const scanedFiles = fs.readdirSync(`./`);
95
-
96
- // update the file hierarchy with updates
97
- exercise.files = exercise.files.filter(f => f.name.includes("test.")).concat(filterFiles(scanedFiles))
98
- Console.debug(`Exercise updated files: `, exercise.files)
99
-
100
- }
101
- // detect lang
102
- const detected = detect(configObject, exercise.files.filter(fileName => entries.includes(fileName.name || fileName)).map(f => f.name || f));
103
-
104
- //if a new language for the testing engine is detected, we replace it
105
- // if not we leave it as it was before
106
- if(configObject.language && !["", "auto"].includes(configObject.language)){
107
- Console.debug(`Exercise language ignored, instead imported from configuration ${configObject.language}`)
108
- exercise.language = detected.language;
109
- }
110
- else if(detected.language && (!configObject.language || configObject.language === "auto")){
111
- Console.debug(`Switching to ${detected.language} engine in this exercise`)
112
- exercise.language = detected.language;
113
- }
114
-
115
- // WARNING: has to be the FULL PATH to the entry path
116
- // We need to detect entry in both gradings: Incremental and Isolate
117
- exercise.entry = detected.entry;
118
- Console.debug(`Exercise detected entry: ${detected.entry} and language ${exercise.language}`)
119
-
120
- if(!exercise.graded || config.disableGrading || config.disabledActions.includes("test")) socket.removeAllowed("test")
121
- else socket.addAllowed('test')
122
-
123
- if(!exercise.entry || config.disabledActions.includes("build")){
124
- socket.removeAllowed("build")
125
- Console.debug(`No entry was found for this exercise ${req.params.slug}, looking for the following entries from the config: `, config.entries)
126
- }
127
- else socket.addAllowed('build')
128
-
129
- if(exercise.files.filter(f => !f.name.toLowerCase().includes("readme.") && !f.name.toLowerCase().includes("test.")).length === 0) socket.removeAllowed("reset")
130
- else if(!config.disabledActions.includes("reset")) socket.addAllowed('reset')
131
-
132
- socket.log('ready')
133
-
134
- res.json(exercise)
135
- }))
136
-
137
- app.get('/exercise/:slug/file/:fileName', withHandler((req, res) => {
138
- res.write(configManager.getExercise(req.params.slug).getFile(req.params.fileName))
139
- res.end()
140
- }))
141
-
142
- const textBodyParser = bodyParser.text()
143
- app.put('/exercise/:slug/file/:fileName', textBodyParser, withHandler((req, res) => {
144
- const result = configManager.getExercise(req.params.slug).saveFile(req.params.fileName, req.body)
145
- res.end()
146
- }))
147
-
148
- if(config.outputPath) app.use('/preview', express.static(config.outputPath))
149
-
150
- app.use('/',express.static(config.dirPath+'/_app'))
151
- }