@learnpack/learnpack 2.0.2 → 2.0.3

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.
@@ -1,255 +1,255 @@
1
- import Console from "../../utils/console";
2
- import * as express from "express";
3
- import * as fs from "fs";
4
- import * as bodyParser from "body-parser";
5
- import socket from "../socket";
6
- import queue from "../../utils/fileQueue";
7
- // import gitpod from '../gitpod'
8
- import { detect, filterFiles } from "../config/exercise";
9
- import { IFile } from "../../models/file";
10
- import { IConfigObj, TEntries } from "../../models/config";
11
- import { IConfigManager } from "../../models/config-manager";
12
- import { IExercise } from "../../models/exercise-obj";
13
-
14
- const withHandler =
15
- (func: (req: express.Request, res: express.Response) => void) =>
16
- (req: express.Request, res: express.Response) => {
17
- try {
18
- func(req, res);
19
- } catch (error) {
20
- Console.debug(error);
21
- const _err = {
22
- message: (error as TypeError).message || "There has been an error",
23
- status: (error as any).status || 500,
24
- type: (error as any).type || null,
25
- };
26
- Console.error(_err.message);
27
-
28
- // send rep to the server
29
- res.status(_err.status);
30
- res.json(_err);
31
- }
32
- };
33
-
34
- export default async function (
35
- app: express.Application,
36
- configObject: IConfigObj,
37
- configManager: IConfigManager
38
- ) {
39
- const { config, exercises } = configObject;
40
-
41
- const dispatcher = queue.dispatcher({
42
- create: true,
43
- path: `${config?.dirPath}/vscode_queue.json`,
44
- });
45
- app.get(
46
- "/config",
47
- withHandler((_: express.Request, res: express.Response) => {
48
- res.json(configObject);
49
- })
50
- );
51
-
52
- /**
53
- * TODO: replicate a socket action, the request payload must be passed to the socket as well
54
-
55
- const jsonBodyParser = bodyParser.json()
56
-
57
- type ISocketActions = "addAllowed" | "removeAllowed" | "start" | "on" | "clean" | "ask" | "reload" | "openWindow" | "log" | "emit" | "ready" | "error" | "fatal" | "success" | "onTestingFinished";
58
-
59
- app.post('/socket/:actionName', jsonBodyParser, withHandler((req, res) => {
60
- if (socket[req.params.actionName as ISocketActions] instanceof Function) {
61
- socket[req.params.actionName as ISocketActions](req.body ? req.body.data : null)
62
- res.json({ "details": "Socket call executed sucessfully" })
63
- } else res.status(400).json({ "details": `Socket action ${req.params.actionName} not found` })
64
- }))
65
- */
66
-
67
- // symbolic link to maintain path compatiblity
68
- const fetchStaticAsset = withHandler((req, res) => {
69
- const filePath = `${config?.dirPath}/assets/${req.params.filePath}`;
70
- if (!fs.existsSync(filePath))
71
- throw new Error("File not found: " + filePath);
72
- const content = fs.readFileSync(filePath);
73
- res.write(content);
74
- res.end();
75
- });
76
-
77
- app.get(
78
- `${
79
- config?.dirPath.indexOf("./") === 0 ?
80
- config.dirPath.slice(1) :
81
- config?.dirPath
82
- }/assets/:filePath`,
83
- fetchStaticAsset
84
- );
85
-
86
- app.get("/assets/:filePath", fetchStaticAsset);
87
-
88
- app.get(
89
- "/exercise",
90
- withHandler((_: express.Request, res: express.Response) => {
91
- res.json(exercises);
92
- })
93
- );
94
-
95
- app.get(
96
- "/exercise/:slug/readme",
97
- withHandler(
98
- (
99
- { params: { slug }, query: { lang } }: express.Request,
100
- res: express.Response
101
- ) => {
102
- const excercise: IExercise = configManager.getExercise(slug);
103
-
104
- if (excercise) {
105
- const readme = excercise.getReadme((lang as string) || null);
106
- res.json(readme);
107
- } else {
108
- res.status(400);
109
- }
110
- }
111
- )
112
- );
113
-
114
- app.get(
115
- "/exercise/:slug/report",
116
- withHandler(
117
- ({ params: { slug } }: express.Request, res: express.Response) => {
118
- const report = configManager.getExercise(slug).getTestReport();
119
- res.json(JSON.stringify(report));
120
- }
121
- )
122
- );
123
-
124
- app.get(
125
- "/exercise/:slug",
126
- withHandler((req: express.Request, res: express.Response) => {
127
- // no need to re-start exercise if it's already started
128
- if (
129
- configObject.currentExercise &&
130
- req.params.slug === configObject.currentExercise
131
- ) {
132
- const exercise = configManager.getExercise(req.params.slug);
133
- res.json(exercise);
134
- return;
135
- }
136
-
137
- const exercise = configManager.startExercise(req.params.slug);
138
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
139
-
140
- type TEntry = "python3" | "html" | "node" | "react" | "java";
141
-
142
- const entries = new Set(
143
- Object.keys(config?.entries!).map(
144
- lang => config?.entries[lang as TEntry]
145
- )
146
- );
147
-
148
- // if we are in incremental grading, the entry file can by dinamically detected
149
- // based on the changes the student is making during the exercise
150
- if (config?.grading === "incremental") {
151
- const scanedFiles = fs.readdirSync("./");
152
-
153
- // update the file hierarchy with updates
154
- exercise.files = [
155
- ...exercise.files.filter(f => f.name.includes("test.")),
156
- ...filterFiles(scanedFiles),
157
- ];
158
- Console.debug(`Exercise updated files: `, exercise.files);
159
- }
160
-
161
- const detected = detect(
162
- configObject,
163
- exercise.files
164
- .filter(fileName => entries.has(fileName.name))
165
- .map(f => f.name || f) as string[]
166
- );
167
-
168
- // if a new language for the testing engine is detected, we replace it
169
- // if not we leave it as it was before
170
- if (config?.language && !["", "auto"].includes(config?.language)) {
171
- Console.debug(
172
- `Exercise language ignored, instead imported from configuration ${config?.language}`
173
- );
174
- exercise.language = detected?.language;
175
- } else if (
176
- detected?.language &&
177
- (!config?.language || config?.language === "auto")
178
- ) {
179
- Console.debug(
180
- `Switching to ${detected.language} engine in this exercise`
181
- );
182
- exercise.language = detected.language;
183
- }
184
-
185
- // WARNING: has to be the FULL PATH to the entry path
186
- // We need to detect entry in both gradings: Incremental and Isolate
187
- exercise.entry = detected?.entry;
188
- Console.debug(
189
- `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
190
- );
191
-
192
- if (
193
- !exercise.graded ||
194
- config?.disableGrading ||
195
- config?.disabledActions?.includes("test")
196
- ) {
197
- socket.removeAllowed("test");
198
- } else {
199
- socket.addAllowed("test");
200
- }
201
-
202
- if (!exercise.entry || config?.disabledActions?.includes("build")) {
203
- socket.removeAllowed("build");
204
- } else {
205
- socket.addAllowed("build");
206
- }
207
-
208
- if (
209
- exercise.files.filter(
210
- (f: IFile) =>
211
- !f.name.toLowerCase().includes("readme.") &&
212
- !f.name.toLowerCase().includes("test.")
213
- ).length === 0 ||
214
- config?.disabledActions?.includes("reset")
215
- ) {
216
- socket.removeAllowed("reset");
217
- } else if (!config?.disabledActions?.includes("reset")) {
218
- socket.addAllowed("reset");
219
- }
220
-
221
- socket.log("ready");
222
-
223
- res.json(exercise);
224
- })
225
- );
226
-
227
- app.get(
228
- "/exercise/:slug/file/:fileName",
229
- withHandler((req: express.Request, res: express.Response) => {
230
- res.write(
231
- configManager.getExercise(req.params.slug).getFile(req.params.fileName)
232
- );
233
- res.end();
234
- })
235
- );
236
-
237
- const textBodyParser = bodyParser.text();
238
- app.put(
239
- "/exercise/:slug/file/:fileName",
240
- textBodyParser,
241
- withHandler((req: express.Request, res: express.Response) => {
242
- // const result =
243
- configManager
244
- .getExercise(req.params.slug)
245
- .saveFile(req.params.fileName, req.body);
246
- res.end();
247
- })
248
- );
249
-
250
- if (config?.outputPath) {
251
- app.use("/preview", express.static(config.outputPath));
252
- }
253
-
254
- app.use("/", express.static(`${config?.dirPath}/_app`));
255
- }
1
+ import Console from "../../utils/console";
2
+ import * as express from "express";
3
+ import * as fs from "fs";
4
+ import * as bodyParser from "body-parser";
5
+ import socket from "../socket";
6
+ import queue from "../../utils/fileQueue";
7
+ // import gitpod from '../gitpod'
8
+ import { detect, filterFiles } from "../config/exercise";
9
+ import { IFile } from "../../models/file";
10
+ import { IConfigObj, TEntries } from "../../models/config";
11
+ import { IConfigManager } from "../../models/config-manager";
12
+ import { IExercise } from "../../models/exercise-obj";
13
+
14
+ const withHandler =
15
+ (func: (req: express.Request, res: express.Response) => void) =>
16
+ (req: express.Request, res: express.Response) => {
17
+ try {
18
+ func(req, res);
19
+ } catch (error) {
20
+ Console.debug(error);
21
+ const _err = {
22
+ message: (error as TypeError).message || "There has been an error",
23
+ status: (error as any).status || 500,
24
+ type: (error as any).type || null,
25
+ };
26
+ Console.error(_err.message);
27
+
28
+ // send rep to the server
29
+ res.status(_err.status);
30
+ res.json(_err);
31
+ }
32
+ };
33
+
34
+ export default async function (
35
+ app: express.Application,
36
+ configObject: IConfigObj,
37
+ configManager: IConfigManager
38
+ ) {
39
+ const { config, exercises } = configObject;
40
+
41
+ const dispatcher = queue.dispatcher({
42
+ create: true,
43
+ path: `${config?.dirPath}/vscode_queue.json`,
44
+ });
45
+ app.get(
46
+ "/config",
47
+ withHandler((_: express.Request, res: express.Response) => {
48
+ res.json(configObject);
49
+ })
50
+ );
51
+
52
+ /**
53
+ * TODO: replicate a socket action, the request payload must be passed to the socket as well
54
+
55
+ const jsonBodyParser = bodyParser.json()
56
+
57
+ type ISocketActions = "addAllowed" | "removeAllowed" | "start" | "on" | "clean" | "ask" | "reload" | "openWindow" | "log" | "emit" | "ready" | "error" | "fatal" | "success" | "onTestingFinished";
58
+
59
+ app.post('/socket/:actionName', jsonBodyParser, withHandler((req, res) => {
60
+ if (socket[req.params.actionName as ISocketActions] instanceof Function) {
61
+ socket[req.params.actionName as ISocketActions](req.body ? req.body.data : null)
62
+ res.json({ "details": "Socket call executed sucessfully" })
63
+ } else res.status(400).json({ "details": `Socket action ${req.params.actionName} not found` })
64
+ }))
65
+ */
66
+
67
+ // symbolic link to maintain path compatiblity
68
+ const fetchStaticAsset = withHandler((req, res) => {
69
+ const filePath = `${config?.dirPath}/assets/${req.params.filePath}`;
70
+ if (!fs.existsSync(filePath))
71
+ throw new Error("File not found: " + filePath);
72
+ const content = fs.readFileSync(filePath);
73
+ res.write(content);
74
+ res.end();
75
+ });
76
+
77
+ app.get(
78
+ `${
79
+ config?.dirPath.indexOf("./") === 0 ?
80
+ config.dirPath.slice(1) :
81
+ config?.dirPath
82
+ }/assets/:filePath`,
83
+ fetchStaticAsset
84
+ );
85
+
86
+ app.get("/assets/:filePath", fetchStaticAsset);
87
+
88
+ app.get(
89
+ "/exercise",
90
+ withHandler((_: express.Request, res: express.Response) => {
91
+ res.json(exercises);
92
+ })
93
+ );
94
+
95
+ app.get(
96
+ "/exercise/:slug/readme",
97
+ withHandler(
98
+ (
99
+ { params: { slug }, query: { lang } }: express.Request,
100
+ res: express.Response
101
+ ) => {
102
+ const excercise: IExercise = configManager.getExercise(slug);
103
+
104
+ if (excercise) {
105
+ const readme = excercise.getReadme((lang as string) || null);
106
+ res.json(readme);
107
+ } else {
108
+ res.status(400);
109
+ }
110
+ }
111
+ )
112
+ );
113
+
114
+ app.get(
115
+ "/exercise/:slug/report",
116
+ withHandler(
117
+ ({ params: { slug } }: express.Request, res: express.Response) => {
118
+ const report = configManager.getExercise(slug).getTestReport();
119
+ res.json(JSON.stringify(report));
120
+ }
121
+ )
122
+ );
123
+
124
+ app.get(
125
+ "/exercise/:slug",
126
+ withHandler((req: express.Request, res: express.Response) => {
127
+ // no need to re-start exercise if it's already started
128
+ if (
129
+ configObject.currentExercise &&
130
+ req.params.slug === configObject.currentExercise
131
+ ) {
132
+ const exercise = configManager.getExercise(req.params.slug);
133
+ res.json(exercise);
134
+ return;
135
+ }
136
+
137
+ const exercise = configManager.startExercise(req.params.slug);
138
+ dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
139
+
140
+ type TEntry = "python3" | "html" | "node" | "react" | "java";
141
+
142
+ const entries = new Set(
143
+ Object.keys(config?.entries!).map(
144
+ lang => config?.entries[lang as TEntry]
145
+ )
146
+ );
147
+
148
+ // if we are in incremental grading, the entry file can by dinamically detected
149
+ // based on the changes the student is making during the exercise
150
+ if (config?.grading === "incremental") {
151
+ const scanedFiles = fs.readdirSync("./");
152
+
153
+ // update the file hierarchy with updates
154
+ exercise.files = [
155
+ ...exercise.files.filter(f => f.name.includes("test.")),
156
+ ...filterFiles(scanedFiles),
157
+ ];
158
+ Console.debug(`Exercise updated files: `, exercise.files);
159
+ }
160
+
161
+ const detected = detect(
162
+ configObject,
163
+ exercise.files
164
+ .filter(fileName => entries.has(fileName.name))
165
+ .map(f => f.name || f) as string[]
166
+ );
167
+
168
+ // if a new language for the testing engine is detected, we replace it
169
+ // if not we leave it as it was before
170
+ if (config?.language && !["", "auto"].includes(config?.language)) {
171
+ Console.debug(
172
+ `Exercise language ignored, instead imported from configuration ${config?.language}`
173
+ );
174
+ exercise.language = detected?.language;
175
+ } else if (
176
+ detected?.language &&
177
+ (!config?.language || config?.language === "auto")
178
+ ) {
179
+ Console.debug(
180
+ `Switching to ${detected.language} engine in this exercise`
181
+ );
182
+ exercise.language = detected.language;
183
+ }
184
+
185
+ // WARNING: has to be the FULL PATH to the entry path
186
+ // We need to detect entry in both gradings: Incremental and Isolate
187
+ exercise.entry = detected?.entry;
188
+ Console.debug(
189
+ `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
190
+ );
191
+
192
+ if (
193
+ !exercise.graded ||
194
+ config?.disableGrading ||
195
+ config?.disabledActions?.includes("test")
196
+ ) {
197
+ socket.removeAllowed("test");
198
+ } else {
199
+ socket.addAllowed("test");
200
+ }
201
+
202
+ if (!exercise.entry || config?.disabledActions?.includes("build")) {
203
+ socket.removeAllowed("build");
204
+ } else {
205
+ socket.addAllowed("build");
206
+ }
207
+
208
+ if (
209
+ exercise.files.filter(
210
+ (f: IFile) =>
211
+ !f.name.toLowerCase().includes("readme.") &&
212
+ !f.name.toLowerCase().includes("test.")
213
+ ).length === 0 ||
214
+ config?.disabledActions?.includes("reset")
215
+ ) {
216
+ socket.removeAllowed("reset");
217
+ } else if (!config?.disabledActions?.includes("reset")) {
218
+ socket.addAllowed("reset");
219
+ }
220
+
221
+ socket.log("ready");
222
+
223
+ res.json(exercise);
224
+ })
225
+ );
226
+
227
+ app.get(
228
+ "/exercise/:slug/file/:fileName",
229
+ withHandler((req: express.Request, res: express.Response) => {
230
+ res.write(
231
+ configManager.getExercise(req.params.slug).getFile(req.params.fileName)
232
+ );
233
+ res.end();
234
+ })
235
+ );
236
+
237
+ const textBodyParser = bodyParser.text();
238
+ app.put(
239
+ "/exercise/:slug/file/:fileName",
240
+ textBodyParser,
241
+ withHandler((req: express.Request, res: express.Response) => {
242
+ // const result =
243
+ configManager
244
+ .getExercise(req.params.slug)
245
+ .saveFile(req.params.fileName, req.body);
246
+ res.end();
247
+ })
248
+ );
249
+
250
+ if (config?.outputPath) {
251
+ app.use("/preview", express.static(config.outputPath));
252
+ }
253
+
254
+ app.use("/", express.static(`${config?.dirPath}/_app`));
255
+ }