@learnpack/learnpack 2.1.39 → 2.1.41

Sign up to get free protection for your applications and to get access to all the features.
Files changed (196) hide show
  1. package/README.md +370 -35
  2. package/bin/run +17 -17
  3. package/bin/run.cmd +3 -3
  4. package/lib/commands/audit.d.ts +6 -6
  5. package/lib/commands/audit.js +342 -342
  6. package/lib/commands/clean.d.ts +8 -8
  7. package/lib/commands/clean.js +25 -25
  8. package/lib/commands/download.d.ts +13 -13
  9. package/lib/commands/download.js +55 -55
  10. package/lib/commands/init.d.ts +9 -9
  11. package/lib/commands/init.js +123 -123
  12. package/lib/commands/login.d.ts +14 -14
  13. package/lib/commands/login.js +37 -37
  14. package/lib/commands/logout.d.ts +14 -14
  15. package/lib/commands/logout.js +37 -37
  16. package/lib/commands/publish.d.ts +14 -14
  17. package/lib/commands/publish.js +82 -82
  18. package/lib/commands/start.d.ts +7 -7
  19. package/lib/commands/start.js +239 -208
  20. package/lib/commands/test.d.ts +6 -6
  21. package/lib/commands/test.js +62 -62
  22. package/lib/index.d.ts +1 -1
  23. package/lib/index.js +4 -4
  24. package/lib/managers/config/allowed_files.d.ts +5 -5
  25. package/lib/managers/config/allowed_files.js +30 -30
  26. package/lib/managers/config/defaults.d.ts +41 -41
  27. package/lib/managers/config/defaults.js +44 -44
  28. package/lib/managers/config/exercise.d.ts +36 -36
  29. package/lib/managers/config/exercise.js +236 -236
  30. package/lib/managers/config/index.d.ts +3 -3
  31. package/lib/managers/config/index.js +337 -337
  32. package/lib/managers/file.d.ts +14 -14
  33. package/lib/managers/file.js +153 -153
  34. package/lib/managers/gitpod.d.ts +3 -3
  35. package/lib/managers/gitpod.js +67 -67
  36. package/lib/managers/server/index.d.ts +6 -6
  37. package/lib/managers/server/index.js +58 -58
  38. package/lib/managers/server/routes.d.ts +4 -4
  39. package/lib/managers/server/routes.js +219 -219
  40. package/lib/managers/session.d.ts +3 -3
  41. package/lib/managers/session.js +125 -125
  42. package/lib/managers/socket.d.ts +3 -3
  43. package/lib/managers/socket.js +176 -176
  44. package/lib/managers/telemetry.d.ts +74 -74
  45. package/lib/managers/telemetry.js +206 -206
  46. package/lib/managers/test.js +84 -84
  47. package/lib/models/action.d.ts +2 -2
  48. package/lib/models/action.js +2 -2
  49. package/lib/models/audit.d.ts +15 -15
  50. package/lib/models/audit.js +2 -2
  51. package/lib/models/config-manager.d.ts +21 -21
  52. package/lib/models/config-manager.js +2 -2
  53. package/lib/models/config.d.ts +68 -67
  54. package/lib/models/config.js +2 -2
  55. package/lib/models/counter.d.ts +11 -11
  56. package/lib/models/counter.js +2 -2
  57. package/lib/models/errors.d.ts +15 -15
  58. package/lib/models/errors.js +2 -2
  59. package/lib/models/exercise-obj.d.ts +30 -30
  60. package/lib/models/exercise-obj.js +2 -2
  61. package/lib/models/file.d.ts +5 -5
  62. package/lib/models/file.js +2 -2
  63. package/lib/models/findings.d.ts +17 -17
  64. package/lib/models/findings.js +2 -2
  65. package/lib/models/flags.d.ts +10 -10
  66. package/lib/models/flags.js +2 -2
  67. package/lib/models/front-matter.d.ts +11 -11
  68. package/lib/models/front-matter.js +2 -2
  69. package/lib/models/gitpod-data.d.ts +16 -16
  70. package/lib/models/gitpod-data.js +2 -2
  71. package/lib/models/language.d.ts +4 -4
  72. package/lib/models/language.js +2 -2
  73. package/lib/models/package.d.ts +7 -7
  74. package/lib/models/package.js +2 -2
  75. package/lib/models/plugin-config.d.ts +16 -16
  76. package/lib/models/plugin-config.js +2 -2
  77. package/lib/models/session.d.ts +31 -31
  78. package/lib/models/session.js +2 -2
  79. package/lib/models/socket.d.ts +36 -32
  80. package/lib/models/socket.js +2 -2
  81. package/lib/models/status.d.ts +1 -1
  82. package/lib/models/status.js +2 -2
  83. package/lib/models/success-types.d.ts +1 -1
  84. package/lib/models/success-types.js +2 -2
  85. package/lib/plugin/command/compile.d.ts +6 -6
  86. package/lib/plugin/command/compile.js +18 -18
  87. package/lib/plugin/command/test.d.ts +6 -6
  88. package/lib/plugin/command/test.js +25 -25
  89. package/lib/plugin/index.d.ts +27 -27
  90. package/lib/plugin/index.js +7 -7
  91. package/lib/plugin/plugin.d.ts +8 -8
  92. package/lib/plugin/plugin.js +68 -68
  93. package/lib/plugin/utils.d.ts +16 -16
  94. package/lib/plugin/utils.js +58 -58
  95. package/lib/ui/download.d.ts +5 -5
  96. package/lib/ui/download.js +61 -61
  97. package/lib/utils/BaseCommand.d.ts +8 -8
  98. package/lib/utils/BaseCommand.js +41 -41
  99. package/lib/utils/SessionCommand.d.ts +10 -10
  100. package/lib/utils/SessionCommand.js +43 -43
  101. package/lib/utils/api.d.ts +14 -14
  102. package/lib/utils/api.js +255 -255
  103. package/lib/utils/audit.d.ts +16 -16
  104. package/lib/utils/audit.js +303 -303
  105. package/lib/utils/checkNotInstalled.d.ts +2 -0
  106. package/lib/utils/checkNotInstalled.js +36 -0
  107. package/lib/utils/console.d.ts +12 -12
  108. package/lib/utils/console.js +19 -19
  109. package/lib/utils/errors.d.ts +17 -17
  110. package/lib/utils/errors.js +100 -100
  111. package/lib/utils/exercisesQueue.d.ts +9 -9
  112. package/lib/utils/exercisesQueue.js +38 -38
  113. package/lib/utils/fileQueue.d.ts +40 -40
  114. package/lib/utils/fileQueue.js +168 -168
  115. package/lib/utils/misc.d.ts +1 -1
  116. package/lib/utils/misc.js +23 -23
  117. package/lib/utils/osOperations.d.ts +5 -5
  118. package/lib/utils/osOperations.js +72 -72
  119. package/lib/utils/validators.d.ts +5 -5
  120. package/lib/utils/validators.js +17 -17
  121. package/lib/utils/watcher.d.ts +2 -2
  122. package/lib/utils/watcher.js +25 -25
  123. package/oclif.manifest.json +1 -1
  124. package/package.json +139 -139
  125. package/src/commands/audit.ts +443 -443
  126. package/src/commands/clean.ts +29 -29
  127. package/src/commands/download.ts +61 -61
  128. package/src/commands/init.ts +170 -170
  129. package/src/commands/login.ts +42 -42
  130. package/src/commands/logout.ts +43 -43
  131. package/src/commands/publish.ts +107 -107
  132. package/src/commands/start.ts +53 -23
  133. package/src/commands/test.ts +85 -85
  134. package/src/index.ts +1 -1
  135. package/src/managers/config/allowed_files.ts +29 -29
  136. package/src/managers/config/defaults.ts +42 -42
  137. package/src/managers/config/exercise.ts +311 -311
  138. package/src/managers/config/index.ts +455 -455
  139. package/src/managers/file.ts +196 -196
  140. package/src/managers/gitpod.ts +84 -84
  141. package/src/managers/server/index.ts +78 -78
  142. package/src/managers/server/routes.ts +330 -330
  143. package/src/managers/session.ts +145 -145
  144. package/src/managers/socket.ts +250 -250
  145. package/src/managers/telemetry.ts +346 -346
  146. package/src/managers/test.ts +83 -83
  147. package/src/models/action.ts +10 -10
  148. package/src/models/audit.ts +16 -16
  149. package/src/models/config-manager.ts +23 -23
  150. package/src/models/config.ts +5 -3
  151. package/src/models/counter.ts +11 -11
  152. package/src/models/errors.ts +22 -22
  153. package/src/models/exercise-obj.ts +29 -29
  154. package/src/models/file.ts +5 -5
  155. package/src/models/findings.ts +18 -18
  156. package/src/models/flags.ts +10 -10
  157. package/src/models/front-matter.ts +11 -11
  158. package/src/models/gitpod-data.ts +19 -19
  159. package/src/models/language.ts +4 -4
  160. package/src/models/package.ts +7 -7
  161. package/src/models/plugin-config.ts +17 -17
  162. package/src/models/session.ts +34 -34
  163. package/src/models/socket.ts +5 -0
  164. package/src/models/status.ts +16 -16
  165. package/src/models/success-types.ts +1 -1
  166. package/src/plugin/command/compile.ts +17 -17
  167. package/src/plugin/command/test.ts +30 -30
  168. package/src/plugin/index.ts +6 -6
  169. package/src/plugin/plugin.ts +94 -94
  170. package/src/plugin/utils.ts +87 -87
  171. package/src/types/node-fetch.d.ts +1 -1
  172. package/src/ui/download.ts +71 -71
  173. package/src/utils/BaseCommand.ts +48 -48
  174. package/src/utils/SessionCommand.ts +43 -43
  175. package/src/utils/api.ts +303 -303
  176. package/src/utils/audit.ts +393 -393
  177. package/src/utils/checkNotInstalled.ts +46 -0
  178. package/src/utils/console.ts +24 -24
  179. package/src/utils/errors.ts +117 -117
  180. package/src/utils/exercisesQueue.ts +51 -51
  181. package/src/utils/fileQueue.ts +198 -198
  182. package/src/utils/misc.ts +23 -23
  183. package/src/utils/osOperations.ts +79 -79
  184. package/src/utils/templates/gitignore.txt +19 -19
  185. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.es.md +24 -24
  186. package/src/utils/templates/incremental/.learn/exercises/01-hello-world/README.md +24 -24
  187. package/src/utils/templates/incremental/README.ejs +4 -4
  188. package/src/utils/templates/incremental/README.es.ejs +4 -4
  189. package/src/utils/templates/isolated/01-hello-world/README.es.md +26 -26
  190. package/src/utils/templates/isolated/01-hello-world/README.md +26 -26
  191. package/src/utils/templates/isolated/README.ejs +4 -4
  192. package/src/utils/templates/isolated/README.es.ejs +4 -4
  193. package/src/utils/templates/no-grading/README.ejs +4 -4
  194. package/src/utils/templates/no-grading/README.es.ejs +4 -4
  195. package/src/utils/validators.ts +18 -18
  196. package/src/utils/watcher.ts +27 -27
@@ -1,330 +1,330 @@
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
- import SessionManager from "../../managers/session"
14
- import TelemetryManager from "../telemetry"
15
- import { eventManager } from "../../utils/osOperations"
16
-
17
- const withHandler =
18
- (func: (req: express.Request, res: express.Response) => void) =>
19
- (req: express.Request, res: express.Response) => {
20
- try {
21
- func(req, res)
22
- } catch (error) {
23
- Console.debug(error)
24
- const _err = {
25
- message: (error as TypeError).message || "There has been an error",
26
- status: (error as any).status || 500,
27
- type: (error as any).type || null,
28
- }
29
- Console.error(_err.message)
30
-
31
- // send rep to the server
32
- res.status(_err.status)
33
- res.json(_err)
34
- }
35
- }
36
-
37
- export default async function (
38
- app: express.Application,
39
- configObject: IConfigObj,
40
- configManager: IConfigManager
41
- ) {
42
- const { config, exercises } = configObject
43
- const session = await SessionManager.get(configManager?.get())
44
-
45
- const dispatcher = queue.dispatcher({
46
- create: true,
47
- path: `${config?.dirPath}/vscode_queue.json`,
48
- })
49
- app.get(
50
- "/config",
51
- withHandler((_: express.Request, res: express.Response) => {
52
- res.json(configObject)
53
- })
54
- )
55
-
56
- // Added this line to parse the json body
57
-
58
- const jsonBodyParser = bodyParser.json()
59
- // Trying to log in from frontend
60
- app.post(
61
- "/login",
62
- jsonBodyParser,
63
- withHandler(async (req: express.Request, res: express.Response) => {
64
- const email = req.body.email
65
- const password = req.body.password
66
-
67
- SessionManager.destroy()
68
- const payload = await SessionManager.loginWeb(email, password)
69
-
70
- res.json(payload)
71
- })
72
- )
73
- app.post(
74
- "/set-rigobot-token",
75
- jsonBodyParser,
76
- withHandler(async (req: express.Request, res: express.Response) => {
77
- const token = req.body.token
78
- // Ensure token is provided in the request body
79
- if (!token) {
80
- return res.status(400).json({ error: "Token is required" })
81
- }
82
-
83
- try {
84
- const tokenSaved = await SessionManager.setRigoToken(token)
85
- // Check if the token was saved successfully
86
- if (tokenSaved) {
87
- res.json({ status: "ok" })
88
- } else {
89
- res.status(500).json({ error: "Failed to save the token" })
90
- }
91
- } catch {
92
- // Handle any unexpected errors during the process
93
- res.status(500).json({ error: "Internal server error" })
94
- }
95
- })
96
- )
97
- app.get(
98
- "/check/rigo/status",
99
- withHandler(async (_: express.Request, res: express.Response) => {
100
- const payload = await SessionManager.getPayload()
101
-
102
- if (payload && payload.rigobot && payload.rigobot.key) {
103
- res.json({ rigoToken: payload.rigobot.key })
104
- } else {
105
- res
106
- .status(400)
107
- .json({ details: `Rigobot token not found, please log in first!` })
108
- }
109
- })
110
- )
111
-
112
- // symbolic link to maintain path compatiblity
113
- const fetchStaticAsset = withHandler((req, res) => {
114
- const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
115
- if (!fs.existsSync(filePath))
116
- throw new Error("File not found: " + filePath)
117
- const content = fs.readFileSync(filePath)
118
- res.write(content)
119
- res.end()
120
- })
121
-
122
- app.get(
123
- `${
124
- config?.dirPath.indexOf("./") === 0 ?
125
- config.dirPath.slice(1) :
126
- config?.dirPath
127
- }/assets/:filePath`,
128
- fetchStaticAsset
129
- )
130
-
131
- app.get("/assets/:filePath", fetchStaticAsset)
132
-
133
- app.get(
134
- "/exercise",
135
- withHandler((_: express.Request, res: express.Response) => {
136
- res.json(exercises)
137
- })
138
- )
139
-
140
- app.get(
141
- "/exercise/:slug/readme",
142
- withHandler(
143
- (
144
- { params: { slug }, query: { lang } }: express.Request,
145
- res: express.Response
146
- ) => {
147
- const exercise: IExercise = configManager.getExercise(slug)
148
-
149
- if (exercise && exercise.getReadme) {
150
- const readme = exercise.getReadme((lang as string) || null)
151
- res.json(readme)
152
- } else {
153
- res.status(400)
154
- }
155
- }
156
- )
157
- )
158
-
159
- app.get(
160
- "/exercise/:slug/report",
161
- withHandler(
162
- ({ params: { slug } }: express.Request, res: express.Response) => {
163
- const exercise = configManager.getExercise(slug)
164
- if (exercise && exercise.getTestReport) {
165
- const report = exercise.getTestReport()
166
- res.json(JSON.stringify(report))
167
- }
168
- }
169
- )
170
- )
171
-
172
- app.get(
173
- "/exercise/:slug",
174
- withHandler((req: express.Request, res: express.Response) => {
175
- // no need to re-start exercise if it's already started
176
- if (
177
- configObject.currentExercise &&
178
- req.params.slug === configObject.currentExercise
179
- ) {
180
- const exercise = configManager.getExercise(req.params.slug)
181
- res.json(exercise)
182
- if (exercise.position) {
183
- TelemetryManager.registerStepEvent(
184
- exercise.position,
185
- "open_step",
186
- {}
187
- )
188
- }
189
-
190
- return
191
- }
192
-
193
- const exercise = configManager.startExercise(req.params.slug)
194
- if (exercise.position) {
195
- TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
196
- }
197
-
198
- if (configObject.config?.editor.agent === "os") {
199
- eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
200
- } else {
201
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
202
- }
203
-
204
- type TEntry = "python3" | "html" | "node" | "react" | "java";
205
-
206
- const entries = new Set(
207
- Object.keys(config?.entries!).map(
208
- lang => config?.entries[lang as TEntry]
209
- )
210
- )
211
-
212
- // if we are in incremental grading, the entry file can by dinamically detected
213
- // based on the changes the student is making during the exercise
214
- if (config?.grading === "incremental") {
215
- const scanedFiles = fs.readdirSync("./")
216
-
217
- // update the file hierarchy with updates
218
- exercise.files = [
219
- ...exercise.files.filter(f => f.name.includes("test.")),
220
- ...filterFiles(scanedFiles),
221
- ]
222
- Console.debug(`Exercise updated files: `, exercise.files)
223
- }
224
-
225
- const detected = detect(
226
- configObject,
227
- exercise.files
228
- .filter(fileName => entries.has(fileName.name))
229
- .map(f => f.name || f) as string[]
230
- )
231
-
232
- // if a new language for the testing engine is detected, we replace it
233
- // if not we leave it as it was before
234
- if (config?.language && !["", "auto"].includes(config?.language)) {
235
- Console.debug(
236
- `Exercise language ignored, instead imported from configuration ${config?.language}`
237
- )
238
- exercise.language = detected?.language
239
- } else if (
240
- detected?.language &&
241
- (!config?.language || config?.language === "auto")
242
- ) {
243
- Console.debug(
244
- `Switching to ${detected.language} engine in this exercise`
245
- )
246
- exercise.language = detected.language
247
- }
248
-
249
- // WARNING: has to be the FULL PATH to the entry path
250
- // We need to detect entry in both gradings: Incremental and Isolate
251
- exercise.entry = detected?.entry
252
- Console.debug(
253
- `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
254
- )
255
-
256
- // exercises.graded and exercises.disableGrading deprecated.
257
- if (
258
- !exercise.graded ||
259
- config?.disableGrading ||
260
- config?.disabledActions?.includes("test")
261
- ) {
262
- socket.removeAllowed("test")
263
- } else {
264
- socket.addAllowed("test")
265
- }
266
-
267
- if (!exercise.entry || config?.disabledActions?.includes("build")) {
268
- socket.removeAllowed("build")
269
- } else {
270
- socket.addAllowed("build")
271
- }
272
-
273
- if (
274
- exercise.files.filter(
275
- (f: IFile) =>
276
- !f.name.toLowerCase().includes("readme.") &&
277
- !f.name.toLowerCase().includes("test.")
278
- ).length === 0 ||
279
- config?.disabledActions?.includes("reset")
280
- ) {
281
- socket.removeAllowed("reset")
282
- } else if (!config?.disabledActions?.includes("reset")) {
283
- socket.addAllowed("reset")
284
- }
285
-
286
- socket.log("ready")
287
- res.json(exercise)
288
- })
289
- )
290
-
291
- app.get(
292
- "/exercise/:slug/file/:fileName",
293
- withHandler((req: express.Request, res: express.Response) => {
294
- const exercise = configManager.getExercise(req.params.slug)
295
- if (exercise && exercise.getFile) {
296
- res.write(exercise.getFile(req.params.fileName))
297
- res.end()
298
- }
299
- })
300
- )
301
-
302
- /*
303
- app.post(
304
- "/exercise/:slug/file/:fileName",
305
- withHandler((req: express.Request, res: express.Response) => {
306
- get tokens but also, add allowed action for 'generate'
307
- use the sessionManager to keep compatibility with the cli login command.
308
- })
309
- );
310
- */
311
-
312
- const textBodyParser = bodyParser.text()
313
- app.put(
314
- "/exercise/:slug/file/:fileName",
315
- textBodyParser,
316
- withHandler((req: express.Request, res: express.Response) => {
317
- const exercise = configManager.getExercise(req.params.slug)
318
- if (exercise && exercise.saveFile) {
319
- exercise.saveFile(req.params.fileName, req.body)
320
- res.end()
321
- }
322
- })
323
- )
324
-
325
- if (config?.outputPath) {
326
- app.use("/preview", express.static(config.outputPath))
327
- }
328
-
329
- app.use("/", express.static(`${config?.dirPath}/_app`))
330
- }
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
+ import SessionManager from "../../managers/session"
14
+ import TelemetryManager from "../telemetry"
15
+ import { eventManager } from "../../utils/osOperations"
16
+
17
+ const withHandler =
18
+ (func: (req: express.Request, res: express.Response) => void) =>
19
+ (req: express.Request, res: express.Response) => {
20
+ try {
21
+ func(req, res)
22
+ } catch (error) {
23
+ Console.debug(error)
24
+ const _err = {
25
+ message: (error as TypeError).message || "There has been an error",
26
+ status: (error as any).status || 500,
27
+ type: (error as any).type || null,
28
+ }
29
+ Console.error(_err.message)
30
+
31
+ // send rep to the server
32
+ res.status(_err.status)
33
+ res.json(_err)
34
+ }
35
+ }
36
+
37
+ export default async function (
38
+ app: express.Application,
39
+ configObject: IConfigObj,
40
+ configManager: IConfigManager
41
+ ) {
42
+ const { config, exercises } = configObject
43
+ const session = await SessionManager.get(configManager?.get())
44
+
45
+ const dispatcher = queue.dispatcher({
46
+ create: true,
47
+ path: `${config?.dirPath}/vscode_queue.json`,
48
+ })
49
+ app.get(
50
+ "/config",
51
+ withHandler((_: express.Request, res: express.Response) => {
52
+ res.json(configObject)
53
+ })
54
+ )
55
+
56
+ // Added this line to parse the json body
57
+
58
+ const jsonBodyParser = bodyParser.json()
59
+ // Trying to log in from frontend
60
+ app.post(
61
+ "/login",
62
+ jsonBodyParser,
63
+ withHandler(async (req: express.Request, res: express.Response) => {
64
+ const email = req.body.email
65
+ const password = req.body.password
66
+
67
+ SessionManager.destroy()
68
+ const payload = await SessionManager.loginWeb(email, password)
69
+
70
+ res.json(payload)
71
+ })
72
+ )
73
+ app.post(
74
+ "/set-rigobot-token",
75
+ jsonBodyParser,
76
+ withHandler(async (req: express.Request, res: express.Response) => {
77
+ const token = req.body.token
78
+ // Ensure token is provided in the request body
79
+ if (!token) {
80
+ return res.status(400).json({ error: "Token is required" })
81
+ }
82
+
83
+ try {
84
+ const tokenSaved = await SessionManager.setRigoToken(token)
85
+ // Check if the token was saved successfully
86
+ if (tokenSaved) {
87
+ res.json({ status: "ok" })
88
+ } else {
89
+ res.status(500).json({ error: "Failed to save the token" })
90
+ }
91
+ } catch {
92
+ // Handle any unexpected errors during the process
93
+ res.status(500).json({ error: "Internal server error" })
94
+ }
95
+ })
96
+ )
97
+ app.get(
98
+ "/check/rigo/status",
99
+ withHandler(async (_: express.Request, res: express.Response) => {
100
+ const payload = await SessionManager.getPayload()
101
+
102
+ if (payload && payload.rigobot && payload.rigobot.key) {
103
+ res.json({ rigoToken: payload.rigobot.key })
104
+ } else {
105
+ res
106
+ .status(400)
107
+ .json({ details: `Rigobot token not found, please log in first!` })
108
+ }
109
+ })
110
+ )
111
+
112
+ // symbolic link to maintain path compatiblity
113
+ const fetchStaticAsset = withHandler((req, res) => {
114
+ const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
115
+ if (!fs.existsSync(filePath))
116
+ throw new Error("File not found: " + filePath)
117
+ const content = fs.readFileSync(filePath)
118
+ res.write(content)
119
+ res.end()
120
+ })
121
+
122
+ app.get(
123
+ `${
124
+ config?.dirPath.indexOf("./") === 0 ?
125
+ config.dirPath.slice(1) :
126
+ config?.dirPath
127
+ }/assets/:filePath`,
128
+ fetchStaticAsset
129
+ )
130
+
131
+ app.get("/assets/:filePath", fetchStaticAsset)
132
+
133
+ app.get(
134
+ "/exercise",
135
+ withHandler((_: express.Request, res: express.Response) => {
136
+ res.json(exercises)
137
+ })
138
+ )
139
+
140
+ app.get(
141
+ "/exercise/:slug/readme",
142
+ withHandler(
143
+ (
144
+ { params: { slug }, query: { lang } }: express.Request,
145
+ res: express.Response
146
+ ) => {
147
+ const exercise: IExercise = configManager.getExercise(slug)
148
+
149
+ if (exercise && exercise.getReadme) {
150
+ const readme = exercise.getReadme((lang as string) || null)
151
+ res.json(readme)
152
+ } else {
153
+ res.status(400)
154
+ }
155
+ }
156
+ )
157
+ )
158
+
159
+ app.get(
160
+ "/exercise/:slug/report",
161
+ withHandler(
162
+ ({ params: { slug } }: express.Request, res: express.Response) => {
163
+ const exercise = configManager.getExercise(slug)
164
+ if (exercise && exercise.getTestReport) {
165
+ const report = exercise.getTestReport()
166
+ res.json(JSON.stringify(report))
167
+ }
168
+ }
169
+ )
170
+ )
171
+
172
+ app.get(
173
+ "/exercise/:slug",
174
+ withHandler((req: express.Request, res: express.Response) => {
175
+ // no need to re-start exercise if it's already started
176
+ if (
177
+ configObject.currentExercise &&
178
+ req.params.slug === configObject.currentExercise
179
+ ) {
180
+ const exercise = configManager.getExercise(req.params.slug)
181
+ res.json(exercise)
182
+ if (exercise.position) {
183
+ TelemetryManager.registerStepEvent(
184
+ exercise.position,
185
+ "open_step",
186
+ {}
187
+ )
188
+ }
189
+
190
+ return
191
+ }
192
+
193
+ const exercise = configManager.startExercise(req.params.slug)
194
+ if (exercise.position) {
195
+ TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
196
+ }
197
+
198
+ if (configObject.config?.editor.agent === "os") {
199
+ eventManager.enqueue(dispatcher.events.START_EXERCISE, exercise)
200
+ } else {
201
+ dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
202
+ }
203
+
204
+ type TEntry = "python3" | "html" | "node" | "react" | "java";
205
+
206
+ const entries = new Set(
207
+ Object.keys(config?.entries!).map(
208
+ lang => config?.entries[lang as TEntry]
209
+ )
210
+ )
211
+
212
+ // if we are in incremental grading, the entry file can by dinamically detected
213
+ // based on the changes the student is making during the exercise
214
+ if (config?.grading === "incremental") {
215
+ const scanedFiles = fs.readdirSync("./")
216
+
217
+ // update the file hierarchy with updates
218
+ exercise.files = [
219
+ ...exercise.files.filter(f => f.name.includes("test.")),
220
+ ...filterFiles(scanedFiles),
221
+ ]
222
+ Console.debug(`Exercise updated files: `, exercise.files)
223
+ }
224
+
225
+ const detected = detect(
226
+ configObject,
227
+ exercise.files
228
+ .filter(fileName => entries.has(fileName.name))
229
+ .map(f => f.name || f) as string[]
230
+ )
231
+
232
+ // if a new language for the testing engine is detected, we replace it
233
+ // if not we leave it as it was before
234
+ if (config?.language && !["", "auto"].includes(config?.language)) {
235
+ Console.debug(
236
+ `Exercise language ignored, instead imported from configuration ${config?.language}`
237
+ )
238
+ exercise.language = detected?.language
239
+ } else if (
240
+ detected?.language &&
241
+ (!config?.language || config?.language === "auto")
242
+ ) {
243
+ Console.debug(
244
+ `Switching to ${detected.language} engine in this exercise`
245
+ )
246
+ exercise.language = detected.language
247
+ }
248
+
249
+ // WARNING: has to be the FULL PATH to the entry path
250
+ // We need to detect entry in both gradings: Incremental and Isolate
251
+ exercise.entry = detected?.entry
252
+ Console.debug(
253
+ `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
254
+ )
255
+
256
+ // exercises.graded and exercises.disableGrading deprecated.
257
+ if (
258
+ !exercise.graded ||
259
+ config?.disableGrading ||
260
+ config?.disabledActions?.includes("test")
261
+ ) {
262
+ socket.removeAllowed("test")
263
+ } else {
264
+ socket.addAllowed("test")
265
+ }
266
+
267
+ if (!exercise.entry || config?.disabledActions?.includes("build")) {
268
+ socket.removeAllowed("build")
269
+ } else {
270
+ socket.addAllowed("build")
271
+ }
272
+
273
+ if (
274
+ exercise.files.filter(
275
+ (f: IFile) =>
276
+ !f.name.toLowerCase().includes("readme.") &&
277
+ !f.name.toLowerCase().includes("test.")
278
+ ).length === 0 ||
279
+ config?.disabledActions?.includes("reset")
280
+ ) {
281
+ socket.removeAllowed("reset")
282
+ } else if (!config?.disabledActions?.includes("reset")) {
283
+ socket.addAllowed("reset")
284
+ }
285
+
286
+ socket.log("ready")
287
+ res.json(exercise)
288
+ })
289
+ )
290
+
291
+ app.get(
292
+ "/exercise/:slug/file/:fileName",
293
+ withHandler((req: express.Request, res: express.Response) => {
294
+ const exercise = configManager.getExercise(req.params.slug)
295
+ if (exercise && exercise.getFile) {
296
+ res.write(exercise.getFile(req.params.fileName))
297
+ res.end()
298
+ }
299
+ })
300
+ )
301
+
302
+ /*
303
+ app.post(
304
+ "/exercise/:slug/file/:fileName",
305
+ withHandler((req: express.Request, res: express.Response) => {
306
+ get tokens but also, add allowed action for 'generate'
307
+ use the sessionManager to keep compatibility with the cli login command.
308
+ })
309
+ );
310
+ */
311
+
312
+ const textBodyParser = bodyParser.text()
313
+ app.put(
314
+ "/exercise/:slug/file/:fileName",
315
+ textBodyParser,
316
+ withHandler((req: express.Request, res: express.Response) => {
317
+ const exercise = configManager.getExercise(req.params.slug)
318
+ if (exercise && exercise.saveFile) {
319
+ exercise.saveFile(req.params.fileName, req.body)
320
+ res.end()
321
+ }
322
+ })
323
+ )
324
+
325
+ if (config?.outputPath) {
326
+ app.use("/preview", express.static(config.outputPath))
327
+ }
328
+
329
+ app.use("/", express.static(`${config?.dirPath}/_app`))
330
+ }