@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.
- package/README.md +51 -398
- package/bin/run +14 -2
- package/oclif.manifest.json +1 -1
- package/package.json +135 -111
- package/src/commands/audit.ts +462 -0
- package/src/commands/clean.ts +29 -0
- package/src/commands/download.ts +62 -0
- package/src/commands/init.ts +169 -0
- package/src/commands/login.ts +42 -0
- package/src/commands/logout.ts +43 -0
- package/src/commands/publish.ts +107 -0
- package/src/commands/start.ts +229 -0
- package/src/commands/{test.js → test.ts} +19 -21
- package/src/index.ts +1 -0
- package/src/managers/config/allowed_files.ts +29 -0
- package/src/managers/config/defaults.ts +33 -0
- package/src/managers/config/exercise.ts +295 -0
- package/src/managers/config/index.ts +411 -0
- package/src/managers/file.ts +169 -0
- package/src/managers/gitpod.ts +84 -0
- package/src/managers/server/{index.js → index.ts} +26 -19
- package/src/managers/server/routes.ts +250 -0
- package/src/managers/session.ts +118 -0
- package/src/managers/socket.ts +239 -0
- package/src/managers/test.ts +83 -0
- package/src/models/action.ts +3 -0
- package/src/models/audit-errors.ts +4 -0
- package/src/models/config-manager.ts +23 -0
- package/src/models/config.ts +74 -0
- package/src/models/counter.ts +11 -0
- package/src/models/errors.ts +22 -0
- package/src/models/exercise-obj.ts +26 -0
- package/src/models/file.ts +5 -0
- package/src/models/findings.ts +18 -0
- package/src/models/flags.ts +10 -0
- package/src/models/front-matter.ts +11 -0
- package/src/models/gitpod-data.ts +19 -0
- package/src/models/language.ts +4 -0
- package/src/models/package.ts +7 -0
- package/src/models/plugin-config.ts +17 -0
- package/src/models/session.ts +26 -0
- package/src/models/socket.ts +48 -0
- package/src/models/status.ts +15 -0
- package/src/models/success-types.ts +1 -0
- package/src/plugin/command/compile.ts +17 -0
- package/src/plugin/command/test.ts +30 -0
- package/src/plugin/index.ts +6 -0
- package/src/plugin/plugin.ts +94 -0
- package/src/plugin/utils.ts +87 -0
- package/src/types/node-fetch.d.ts +1 -0
- package/src/ui/download.ts +71 -0
- package/src/utils/BaseCommand.ts +48 -0
- package/src/utils/SessionCommand.ts +48 -0
- package/src/utils/api.ts +194 -0
- package/src/utils/audit.ts +162 -0
- package/src/utils/console.ts +24 -0
- package/src/utils/errors.ts +117 -0
- package/src/utils/{exercisesQueue.js → exercisesQueue.ts} +12 -6
- package/src/utils/fileQueue.ts +198 -0
- package/src/utils/misc.ts +23 -0
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +2 -4
- package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +1 -2
- package/src/utils/templates/isolated/01-hello-world/README.es.md +1 -2
- package/src/utils/templates/isolated/01-hello-world/README.md +1 -2
- package/src/utils/templates/isolated/README.ejs +1 -1
- package/src/utils/templates/isolated/README.es.ejs +1 -1
- package/src/utils/validators.ts +18 -0
- package/src/utils/watcher.ts +27 -0
- package/plugin/command/compile.js +0 -17
- package/plugin/command/test.js +0 -29
- package/plugin/index.js +0 -6
- package/plugin/plugin.js +0 -71
- package/plugin/utils.js +0 -78
- package/src/commands/audit.js +0 -243
- package/src/commands/clean.js +0 -27
- package/src/commands/download.js +0 -52
- package/src/commands/hello.js +0 -20
- package/src/commands/init.js +0 -133
- package/src/commands/login.js +0 -45
- package/src/commands/logout.js +0 -39
- package/src/commands/publish.js +0 -78
- package/src/commands/start.js +0 -169
- package/src/index.js +0 -1
- package/src/managers/config/allowed_files.js +0 -12
- package/src/managers/config/defaults.js +0 -32
- package/src/managers/config/exercise.js +0 -212
- package/src/managers/config/index.js +0 -342
- package/src/managers/file.js +0 -137
- package/src/managers/server/routes.js +0 -151
- package/src/managers/session.js +0 -83
- package/src/managers/socket.js +0 -185
- package/src/managers/test.js +0 -77
- package/src/ui/download.js +0 -48
- package/src/utils/BaseCommand.js +0 -34
- package/src/utils/SessionCommand.js +0 -46
- package/src/utils/api.js +0 -164
- package/src/utils/audit.js +0 -114
- package/src/utils/console.js +0 -16
- package/src/utils/errors.js +0 -90
- package/src/utils/fileQueue.js +0 -194
- package/src/utils/misc.js +0 -26
- package/src/utils/validators.js +0 -15
- 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
|
-
}
|
package/src/managers/file.js
DELETED
@@ -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
|
-
}
|