@learnpack/learnpack 2.1.26 → 2.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +10 -10
  2. package/lib/commands/start.js +15 -4
  3. package/lib/managers/file.d.ts +1 -0
  4. package/lib/managers/file.js +8 -1
  5. package/lib/managers/server/routes.js +48 -14
  6. package/lib/managers/session.d.ts +1 -1
  7. package/lib/managers/session.js +39 -12
  8. package/lib/managers/socket.d.ts +1 -1
  9. package/lib/managers/socket.js +57 -43
  10. package/lib/models/action.d.ts +1 -1
  11. package/lib/models/config.d.ts +1 -1
  12. package/lib/models/exercise-obj.d.ts +3 -0
  13. package/lib/models/session.d.ts +4 -1
  14. package/lib/models/socket.d.ts +7 -6
  15. package/lib/models/status.d.ts +1 -1
  16. package/lib/utils/api.d.ts +2 -0
  17. package/lib/utils/api.js +51 -6
  18. package/oclif.manifest.json +1 -1
  19. package/package.json +3 -1
  20. package/src/commands/audit.ts +113 -113
  21. package/src/commands/clean.ts +10 -10
  22. package/src/commands/download.ts +18 -18
  23. package/src/commands/init.ts +39 -39
  24. package/src/commands/login.ts +13 -13
  25. package/src/commands/logout.ts +9 -9
  26. package/src/commands/publish.ts +25 -25
  27. package/src/commands/start.ts +101 -75
  28. package/src/commands/test.ts +34 -34
  29. package/src/managers/config/allowed_files.ts +2 -2
  30. package/src/managers/config/defaults.ts +2 -2
  31. package/src/managers/config/exercise.ts +79 -79
  32. package/src/managers/config/index.ts +145 -145
  33. package/src/managers/file.ts +74 -65
  34. package/src/managers/server/index.ts +32 -31
  35. package/src/managers/server/routes.ts +139 -90
  36. package/src/managers/session.ts +53 -24
  37. package/src/managers/socket.ts +92 -79
  38. package/src/models/action.ts +8 -1
  39. package/src/models/config-manager.ts +2 -2
  40. package/src/models/config.ts +7 -2
  41. package/src/models/exercise-obj.ts +6 -3
  42. package/src/models/plugin-config.ts +2 -2
  43. package/src/models/session.ts +5 -2
  44. package/src/models/socket.ts +12 -6
  45. package/src/models/status.ts +15 -14
  46. package/src/plugin/command/compile.ts +10 -10
  47. package/src/plugin/command/test.ts +14 -14
  48. package/src/plugin/index.ts +5 -5
  49. package/src/plugin/plugin.ts +26 -26
  50. package/src/plugin/utils.ts +23 -23
  51. package/src/utils/BaseCommand.ts +16 -16
  52. package/src/utils/api.ts +143 -91
  53. package/src/utils/audit.ts +93 -96
  54. package/src/utils/exercisesQueue.ts +15 -15
  55. package/src/utils/fileQueue.ts +85 -85
  56. package/src/utils/watcher.ts +14 -14
@@ -1,78 +1,117 @@
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";
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
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";
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"
13
14
 
14
15
  const withHandler =
15
16
  (func: (req: express.Request, res: express.Response) => void) =>
16
17
  (req: express.Request, res: express.Response) => {
17
18
  try {
18
- func(req, res);
19
+ func(req, res)
19
20
  } catch (error) {
20
- Console.debug(error);
21
+ Console.debug(error)
21
22
  const _err = {
22
23
  message: (error as TypeError).message || "There has been an error",
23
24
  status: (error as any).status || 500,
24
25
  type: (error as any).type || null,
25
- };
26
- Console.error(_err.message);
26
+ }
27
+ Console.error(_err.message)
27
28
 
28
29
  // send rep to the server
29
- res.status(_err.status);
30
- res.json(_err);
30
+ res.status(_err.status)
31
+ res.json(_err)
31
32
  }
32
- };
33
+ }
33
34
 
34
35
  export default async function (
35
36
  app: express.Application,
36
37
  configObject: IConfigObj,
37
38
  configManager: IConfigManager
38
39
  ) {
39
- const { config, exercises } = configObject;
40
+ const { config, exercises } = configObject
41
+ const session = await SessionManager.get(configManager?.get())
40
42
 
41
43
  const dispatcher = queue.dispatcher({
42
44
  create: true,
43
45
  path: `${config?.dirPath}/vscode_queue.json`,
44
- });
46
+ })
45
47
  app.get(
46
48
  "/config",
47
49
  withHandler((_: express.Request, res: express.Response) => {
48
- res.json(configObject);
50
+ res.json(configObject)
49
51
  })
50
- );
52
+ )
51
53
 
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
- */
54
+ // Added this line to parse the json body
55
+
56
+ const jsonBodyParser = bodyParser.json()
57
+ // Trying to log in from frontend
58
+ app.post(
59
+ "/login",
60
+ jsonBodyParser,
61
+ withHandler(async (req: express.Request, res: express.Response) => {
62
+ const email = req.body.email
63
+ const password = req.body.password
64
+
65
+ SessionManager.destroy()
66
+ const payload = await SessionManager.loginWeb(email, password)
67
+
68
+ res.json(payload)
69
+ })
70
+ )
71
+ app.post(
72
+ "/set-openai-token",
73
+ jsonBodyParser,
74
+ withHandler(async (req: express.Request, res: express.Response) => {
75
+ const token = req.body.token
76
+ console.log("Setting openai token")
77
+
78
+ const tokenSaved = await SessionManager.setOpenAIToken(token)
79
+ if (tokenSaved) {
80
+ res.json({ status: "ok" })
81
+ } else {
82
+ res.status(400)
83
+ }
84
+ })
85
+ )
86
+
87
+ app.get(
88
+ "/check/rigo/status",
89
+ withHandler(async (_: express.Request, res: express.Response) => {
90
+ const payload = await SessionManager.getPayload()
91
+ const openaiToken = await SessionManager.getOpenAIToken()
92
+ // console.log("Looking Rigo creds");
93
+
94
+ if (payload && payload.rigobot && payload.rigobot.key) {
95
+ res.json({ rigoToken: payload.rigobot.key })
96
+ } else if (openaiToken) {
97
+ res.json({ openaiToken })
98
+ } else {
99
+ res
100
+ .status(400)
101
+ .json({ details: `Rigobot token not found, please log in first!` })
102
+ }
103
+ })
104
+ )
66
105
 
67
106
  // symbolic link to maintain path compatiblity
68
107
  const fetchStaticAsset = withHandler((req, res) => {
69
- const filePath = `${config?.dirPath}/assets/${req.params.filePath}`;
108
+ const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
70
109
  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
- });
110
+ throw new Error("File not found: " + filePath)
111
+ const content = fs.readFileSync(filePath)
112
+ res.write(content)
113
+ res.end()
114
+ })
76
115
 
77
116
  app.get(
78
117
  `${
@@ -81,16 +120,16 @@ export default async function (
81
120
  config?.dirPath
82
121
  }/assets/:filePath`,
83
122
  fetchStaticAsset
84
- );
123
+ )
85
124
 
86
- app.get("/assets/:filePath", fetchStaticAsset);
125
+ app.get("/assets/:filePath", fetchStaticAsset)
87
126
 
88
127
  app.get(
89
128
  "/exercise",
90
129
  withHandler((_: express.Request, res: express.Response) => {
91
- res.json(exercises);
130
+ res.json(exercises)
92
131
  })
93
- );
132
+ )
94
133
 
95
134
  app.get(
96
135
  "/exercise/:slug/readme",
@@ -99,30 +138,30 @@ export default async function (
99
138
  { params: { slug }, query: { lang } }: express.Request,
100
139
  res: express.Response
101
140
  ) => {
102
- const exercise: IExercise = configManager.getExercise(slug);
141
+ const exercise: IExercise = configManager.getExercise(slug)
103
142
 
104
143
  if (exercise && exercise.getReadme) {
105
- const readme = exercise.getReadme((lang as string) || null);
106
- res.json(readme);
144
+ const readme = exercise.getReadme((lang as string) || null)
145
+ res.json(readme)
107
146
  } else {
108
- res.status(400);
147
+ res.status(400)
109
148
  }
110
149
  }
111
150
  )
112
- );
151
+ )
113
152
 
114
153
  app.get(
115
154
  "/exercise/:slug/report",
116
155
  withHandler(
117
156
  ({ params: { slug } }: express.Request, res: express.Response) => {
118
- const exercise = configManager.getExercise(slug);
157
+ const exercise = configManager.getExercise(slug)
119
158
  if (exercise && exercise.getTestReport) {
120
- const report = exercise.getTestReport();
121
- res.json(JSON.stringify(report));
159
+ const report = exercise.getTestReport()
160
+ res.json(JSON.stringify(report))
122
161
  }
123
162
  }
124
163
  )
125
- );
164
+ )
126
165
 
127
166
  app.get(
128
167
  "/exercise/:slug",
@@ -132,13 +171,13 @@ export default async function (
132
171
  configObject.currentExercise &&
133
172
  req.params.slug === configObject.currentExercise
134
173
  ) {
135
- const exercise = configManager.getExercise(req.params.slug);
136
- res.json(exercise);
137
- return;
174
+ const exercise = configManager.getExercise(req.params.slug)
175
+ res.json(exercise)
176
+ return
138
177
  }
139
178
 
140
- const exercise = configManager.startExercise(req.params.slug);
141
- dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug);
179
+ const exercise = configManager.startExercise(req.params.slug)
180
+ dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
142
181
 
143
182
  type TEntry = "python3" | "html" | "node" | "react" | "java";
144
183
 
@@ -146,19 +185,19 @@ export default async function (
146
185
  Object.keys(config?.entries!).map(
147
186
  lang => config?.entries[lang as TEntry]
148
187
  )
149
- );
188
+ )
150
189
 
151
190
  // if we are in incremental grading, the entry file can by dinamically detected
152
191
  // based on the changes the student is making during the exercise
153
192
  if (config?.grading === "incremental") {
154
- const scanedFiles = fs.readdirSync("./");
193
+ const scanedFiles = fs.readdirSync("./")
155
194
 
156
195
  // update the file hierarchy with updates
157
196
  exercise.files = [
158
197
  ...exercise.files.filter(f => f.name.includes("test.")),
159
198
  ...filterFiles(scanedFiles),
160
- ];
161
- Console.debug(`Exercise updated files: `, exercise.files);
199
+ ]
200
+ Console.debug(`Exercise updated files: `, exercise.files)
162
201
  }
163
202
 
164
203
  const detected = detect(
@@ -166,31 +205,31 @@ export default async function (
166
205
  exercise.files
167
206
  .filter(fileName => entries.has(fileName.name))
168
207
  .map(f => f.name || f) as string[]
169
- );
208
+ )
170
209
 
171
210
  // if a new language for the testing engine is detected, we replace it
172
211
  // if not we leave it as it was before
173
212
  if (config?.language && !["", "auto"].includes(config?.language)) {
174
213
  Console.debug(
175
214
  `Exercise language ignored, instead imported from configuration ${config?.language}`
176
- );
177
- exercise.language = detected?.language;
215
+ )
216
+ exercise.language = detected?.language
178
217
  } else if (
179
218
  detected?.language &&
180
219
  (!config?.language || config?.language === "auto")
181
220
  ) {
182
221
  Console.debug(
183
222
  `Switching to ${detected.language} engine in this exercise`
184
- );
185
- exercise.language = detected.language;
223
+ )
224
+ exercise.language = detected.language
186
225
  }
187
226
 
188
227
  // WARNING: has to be the FULL PATH to the entry path
189
228
  // We need to detect entry in both gradings: Incremental and Isolate
190
- exercise.entry = detected?.entry;
229
+ exercise.entry = detected?.entry
191
230
  Console.debug(
192
231
  `Exercise detected entry: ${detected?.entry} and language ${exercise.language}`
193
- );
232
+ )
194
233
 
195
234
  // exercises.graded and exercises.disableGrading deprecated.
196
235
  if (
@@ -198,15 +237,15 @@ export default async function (
198
237
  config?.disableGrading ||
199
238
  config?.disabledActions?.includes("test")
200
239
  ) {
201
- socket.removeAllowed("test");
240
+ socket.removeAllowed("test")
202
241
  } else {
203
- socket.addAllowed("test");
242
+ socket.addAllowed("test")
204
243
  }
205
244
 
206
245
  if (!exercise.entry || config?.disabledActions?.includes("build")) {
207
- socket.removeAllowed("build");
246
+ socket.removeAllowed("build")
208
247
  } else {
209
- socket.addAllowed("build");
248
+ socket.addAllowed("build")
210
249
  }
211
250
 
212
251
  if (
@@ -217,44 +256,54 @@ export default async function (
217
256
  ).length === 0 ||
218
257
  config?.disabledActions?.includes("reset")
219
258
  ) {
220
- socket.removeAllowed("reset");
259
+ socket.removeAllowed("reset")
221
260
  } else if (!config?.disabledActions?.includes("reset")) {
222
- socket.addAllowed("reset");
261
+ socket.addAllowed("reset")
223
262
  }
224
263
 
225
- socket.log("ready");
264
+ socket.log("ready")
226
265
 
227
- res.json(exercise);
266
+ res.json(exercise)
228
267
  })
229
- );
268
+ )
230
269
 
231
270
  app.get(
232
271
  "/exercise/:slug/file/:fileName",
233
272
  withHandler((req: express.Request, res: express.Response) => {
234
- const exercise = configManager.getExercise(req.params.slug);
273
+ const exercise = configManager.getExercise(req.params.slug)
235
274
  if (exercise && exercise.getFile) {
236
- res.write(exercise.getFile(req.params.fileName));
237
- res.end();
275
+ res.write(exercise.getFile(req.params.fileName))
276
+ res.end()
238
277
  }
239
278
  })
279
+ )
280
+
281
+ /*
282
+ app.post(
283
+ "/exercise/:slug/file/:fileName",
284
+ withHandler((req: express.Request, res: express.Response) => {
285
+ get tokens but also, add allowed action for 'generate'
286
+ use the sessionManager to keep compatibility with the cli login command.
287
+ })
240
288
  );
289
+ */
241
290
 
242
- const textBodyParser = bodyParser.text();
291
+ const textBodyParser = bodyParser.text()
243
292
  app.put(
244
293
  "/exercise/:slug/file/:fileName",
245
294
  textBodyParser,
246
295
  withHandler((req: express.Request, res: express.Response) => {
247
- const exercise = configManager.getExercise(req.params.slug);
296
+ const exercise = configManager.getExercise(req.params.slug)
248
297
  if (exercise && exercise.saveFile) {
249
- exercise.saveFile(req.params.fileName, req.body);
250
- res.end();
298
+ exercise.saveFile(req.params.fileName, req.body)
299
+ res.end()
251
300
  }
252
301
  })
253
- );
302
+ )
254
303
 
255
304
  if (config?.outputPath) {
256
- app.use("/preview", express.static(config.outputPath));
305
+ app.use("/preview", express.static(config.outputPath))
257
306
  }
258
307
 
259
- app.use("/", express.static(`${config?.dirPath}/_app`));
308
+ app.use("/", express.static(`${config?.dirPath}/_app`))
260
309
  }
@@ -1,15 +1,15 @@
1
- import Console from '../utils/console'
2
- import api from '../utils/api'
1
+ import Console from "../utils/console"
2
+ import api from "../utils/api"
3
3
 
4
- import v from 'validator'
5
- import {ValidationError, InternalError} from '../utils/errors'
6
- // import moment from 'moment'
7
- import * as fs from 'fs'
8
- import cli from 'cli-ux'
9
- import * as storage from 'node-persist'
4
+ import v from "validator"
5
+ import { ValidationError, InternalError } from "../utils/errors"
10
6
 
11
- import {IPayload, ISession, IStartProps} from '../models/session'
12
- import {IConfigObj} from '../models/config'
7
+ import * as fs from "fs"
8
+ import cli from "cli-ux"
9
+ import * as storage from "node-persist"
10
+
11
+ import { IPayload, ISession, IStartProps } from "../models/session"
12
+ import { IConfigObj } from "../models/config"
13
13
 
14
14
  const Session: ISession = {
15
15
  sessionStarted: false,
@@ -19,34 +19,51 @@ const Session: ISession = {
19
19
  initialize: async function () {
20
20
  if (!this.sessionStarted) {
21
21
  if (!this.config) {
22
- throw InternalError('Configuration not found')
22
+ throw InternalError("Configuration not found")
23
23
  }
24
24
 
25
25
  if (!fs.existsSync(this.config.dirPath)) {
26
26
  fs.mkdirSync(this.config.dirPath)
27
27
  }
28
28
 
29
- await storage.init({dir: `${this.config.dirPath}/.session`})
29
+ await storage.init({ dir: `${this.config.dirPath}/.session` })
30
30
  this.sessionStarted = true
31
31
  }
32
32
 
33
33
  return true
34
34
  },
35
+ getOpenAIToken: async function () {
36
+ await this.initialize()
37
+ let token = null
38
+ try {
39
+ token = await storage.getItem("openai-token")
40
+ } catch {
41
+ Console.debug("Error retriving openai token")
42
+ }
43
+
44
+ return token
45
+ },
46
+ setOpenAIToken: async function (token: string) {
47
+ await this.initialize()
48
+ await storage.setItem("openai-token", token)
49
+ Console.debug("OpenAI token successfuly set")
50
+ return true
51
+ },
35
52
  setPayload: async function (value: IPayload) {
36
53
  await this.initialize()
37
- await storage.setItem('bc-payload', {token: this.token, ...value})
38
- Console.debug('Payload successfuly found and set for ' + value.email)
54
+ await storage.setItem("bc-payload", { token: this.token, ...value })
55
+ Console.debug("Payload successfuly found and set for " + value.email)
39
56
  return true
40
57
  },
41
58
  getPayload: async function () {
42
59
  await this.initialize()
43
60
  let payload = null
44
61
  try {
45
- payload = await storage.getItem('bc-payload')
62
+ payload = await storage.getItem("bc-payload")
46
63
  } catch (error) {
47
64
  // TODO: Remove it
48
65
  console.log(error)
49
- Console.debug('Error retriving session payload')
66
+ Console.debug("Error retriving session payload")
50
67
  }
51
68
 
52
69
  return payload
@@ -77,18 +94,30 @@ const Session: ISession = {
77
94
  }
78
95
  },
79
96
  login: async function () {
80
- const email = await cli.prompt('What is your email?')
97
+ const email = await cli.prompt("What is your email?")
81
98
  if (!v.isEmail(email)) {
82
- throw ValidationError('Invalid email')
99
+ throw ValidationError("Invalid email")
83
100
  }
84
101
 
85
- const password = await cli.prompt('What is your password?', {
86
- type: 'hide',
102
+ const password = await cli.prompt("What is your password?", {
103
+ type: "hide",
87
104
  })
88
105
 
89
106
  const data = await api.login(email, password)
90
107
  if (data) {
91
- this.start({token: data.token, payload: data})
108
+ cli.log(data)
109
+ this.start({ token: data.token, payload: data })
110
+ }
111
+ },
112
+ loginWeb: async function (email, password) {
113
+ if (!v.isEmail(email)) {
114
+ throw ValidationError("Invalid email")
115
+ }
116
+
117
+ const data = await api.login(email, password)
118
+ if (data) {
119
+ this.start({ token: data.token, payload: data })
120
+ return data
92
121
  }
93
122
  },
94
123
  sync: async function () {
@@ -97,9 +126,9 @@ const Session: ISession = {
97
126
  this.token = payload.token
98
127
  }
99
128
  },
100
- start: async function ({token, payload = null}: IStartProps) {
129
+ start: async function ({ token, payload = null }: IStartProps) {
101
130
  if (!token) {
102
- throw new Error('A token and email is needed to start a session')
131
+ throw new Error("A token and email is needed to start a session")
103
132
  }
104
133
 
105
134
  this.token = token
@@ -111,7 +140,7 @@ const Session: ISession = {
111
140
  destroy: async function () {
112
141
  await storage.clear()
113
142
  this.token = null
114
- Console.success('You have logged out')
143
+ Console.success("You have logged out")
115
144
  },
116
145
  }
117
146