@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
@@ -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
- }