@learnpack/learnpack 5.0.30 → 5.0.32

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.
@@ -5,6 +5,7 @@ import * as prompts from "prompts"
5
5
  import { translateExercise } from "../utils/rigoActions"
6
6
  import SessionManager from "../managers/session"
7
7
  import Console from "../utils/console"
8
+ import { saveTranslatedReadme } from "../utils/creatorUtilities"
8
9
 
9
10
  // This function list the names of the exercise directories inside the ./exercises folder, if the exercises folder doesn't exist, it will look for the ./.learn/exercises folder
10
11
  const listExercises = async () => {
@@ -24,11 +25,6 @@ const listExercises = async () => {
24
25
  })
25
26
  }
26
27
 
27
- const cleanReadme = (readme: string) => {
28
- // Replace <text> and </text> with nothing
29
- return readme.replace(/<text>/g, "").replace(/<\/text>/g, "")
30
- }
31
-
32
28
  const getReadmeForExercise = async (exercise: string) => {
33
29
  const readmePath = path.join(
34
30
  process.cwd(),
@@ -39,20 +35,6 @@ const getReadmeForExercise = async (exercise: string) => {
39
35
  return fs.readFileSync(readmePath, "utf8")
40
36
  }
41
37
 
42
- const saveTranslatedReadme = async (
43
- exercise: string,
44
- languageCode: string,
45
- readme: string
46
- ) => {
47
- const readmePath = path.join(
48
- process.cwd(),
49
- "exercises",
50
- exercise,
51
- `README.${languageCode}.md`
52
- )
53
- fs.writeFileSync(readmePath, cleanReadme(readme))
54
- }
55
-
56
38
  export default class BuildCommand extends SessionCommand {
57
39
  static description =
58
40
  "List all the lessons, the user is able of select many of them to translate to the given languages"
@@ -118,7 +100,6 @@ export default class BuildCommand extends SessionCommand {
118
100
  text_to_translate: readme,
119
101
  output_language: language,
120
102
  })
121
- console.log(response, "RESPONSE")
122
103
 
123
104
  await saveTranslatedReadme(
124
105
  exercise,
@@ -510,6 +510,7 @@ fs.mkdirSync(confPath.base)
510
510
  exercise(path, position, configObj)
511
511
  ) :
512
512
  [exercise(configObj?.config?.exercisesPath || "", 0, configObj)]
513
+
513
514
  this.save()
514
515
  },
515
516
  createExercise: (slug: string, content: string, language: string) => {
@@ -12,7 +12,9 @@ import { IConfigManager } from "../../models/config-manager"
12
12
  import { IExercise } from "../../models/exercise-obj"
13
13
  import SessionManager from "../../managers/session"
14
14
  import TelemetryManager from "../telemetry"
15
- import { eventManager } from "../../utils/osOperations"
15
+ import { saveTranslatedReadme } from "../../utils/creatorUtilities"
16
+ import { translateExercise } from "../../utils/rigoActions"
17
+ // import { eventManager } from "../../utils/osOperations"
16
18
 
17
19
  const withHandler =
18
20
  (func: (req: express.Request, res: express.Response) => void) =>
@@ -394,6 +396,64 @@ throw new Error("File not found: " + filePath)
394
396
  })
395
397
  )
396
398
 
399
+ app.post(
400
+ "/actions/translate",
401
+ jsonBodyParser,
402
+ withHandler(async (req: express.Request, res: express.Response) => {
403
+ const { exerciseSlugs, languages } = req.body
404
+
405
+ const session = await SessionManager.getPayload()
406
+ const rigoToken = session?.rigobot?.key
407
+
408
+ if (!rigoToken) {
409
+ return res.status(400).json({ error: "RigoToken not found" })
410
+ }
411
+
412
+ const languagesToTranslate: string[] = languages.split(",")
413
+
414
+ try {
415
+ await Promise.all(
416
+ exerciseSlugs.map(async (slug: string) => {
417
+ const exercise = configManager.getExercise(slug)
418
+ if (!exercise) {
419
+ throw new Error(`Exercise ${slug} not found`)
420
+ }
421
+
422
+ if (exercise.getReadme) {
423
+ const readme = exercise.getReadme(null)
424
+
425
+ await Promise.all(
426
+ languagesToTranslate.map(async (language: string) => {
427
+ const response = await translateExercise(rigoToken, {
428
+ text_to_translate: readme.body,
429
+ output_language: language,
430
+ })
431
+
432
+ await saveTranslatedReadme(
433
+ slug,
434
+ response.parsed.output_language_code,
435
+ response.parsed.translation
436
+ )
437
+
438
+ Console.success(
439
+ `Translated ${slug} to ${language} successfully`
440
+ )
441
+ })
442
+ )
443
+ }
444
+ })
445
+ )
446
+
447
+ configManager.buildIndex()
448
+
449
+ return res.status(200).json({ message: "Translated exercises" })
450
+ } catch (error) {
451
+ console.log(error, "ERROR")
452
+ return res.status(400).json({ error: (error as Error).message })
453
+ }
454
+ })
455
+ )
456
+
397
457
  app.post(
398
458
  "/exercise/:slug/create",
399
459
  jsonBodyParser,
@@ -1,274 +1,274 @@
1
- import { Socket, Server } from "socket.io"
2
- import Console from "../utils/console"
3
- import queue from "../utils/fileQueue"
4
-
5
- import { ISocket, TPossibleActions } from "../models/socket"
6
- import { IConfig } from "../models/config"
7
- import { ICallback, TAction } from "../models/action"
8
- import { IExercise, IExerciseData } from "../models/exercise-obj"
9
- import { TStatus } from "../models/status"
10
- import { TSuccessType } from "../models/success-types"
11
- import * as http from "http"
12
-
13
- const languageToMessage: Record<string, string> = {
14
- python3:
15
- "Your code executed without erros, but no output is shown on the terminal. Maybe you forgot to include a print statement in your code?",
16
- node: "Your code executed without erros, but no output is shown on the terminal. Maybe you forgot to include a console.log statement in your code?",
17
- }
18
-
19
- const SocketManager: ISocket = {
20
- socket: null,
21
- config: null,
22
- allowedActions: [],
23
- possibleActions: ["build", "reset", "test", "tutorial"],
24
- isTestingEnvironment: false,
25
- actionCallBacks: {
26
- clean: (_, s: { logs: Array<string> }) => {
27
- s.logs = []
28
- },
29
- },
30
- addAllowed: function (actions: Array<TPossibleActions> | TPossibleActions) {
31
- if (!Array.isArray(actions))
32
- actions = [actions]
33
-
34
- // avoid adding the "test" action if grading is disabled
35
- if (
36
- actions.includes("test") &&
37
- this.config?.disabledActions?.includes("test")
38
- ) {
39
- actions = actions.filter((a: TPossibleActions) => a !== "test")
40
- }
41
-
42
- this.allowedActions = [
43
- ...(this.allowedActions || []).filter(
44
- (a: TPossibleActions) => !actions.includes(a)
45
- ),
46
- ...actions,
47
- ]
48
- },
49
- removeAllowed: function (
50
- actions: Array<TPossibleActions> | TPossibleActions
51
- ) {
52
- if (!Array.isArray(actions)) {
53
- actions = [actions]
54
- }
55
-
56
- this.allowedActions = (this.allowedActions || []).filter(
57
- (a: TPossibleActions) => !actions.includes(a)
58
- )
59
- },
60
- start: function (
61
- config: IConfig,
62
- server: http.Server,
63
- isTestingEnvironment = false
64
- ) {
65
- this.config = config
66
- this.isTestingEnvironment = isTestingEnvironment
67
- this.socket = new Server(server, {
68
- allowEIO3: true,
69
- cors: {
70
- origin: "http://localhost:5173",
71
- methods: ["GET", "POST"],
72
- },
73
- })
74
-
75
- this.allowedActions =
76
- this.config?.disabledActions?.includes("test") ||
77
- this.config?.disableGrading ?
78
- this.possibleActions.filter(
79
- a => !this.config?.disabledActions?.includes(a) && a !== "test"
80
- ) :
81
- this.possibleActions.filter(a => !this.allowedActions?.includes(a))
82
-
83
- if (this.config?.grading === "incremental") {
84
- this.removeAllowed("reset")
85
- }
86
-
87
- if (this.socket) {
88
- this.socket.on("connection", (socket: Socket) => {
89
- Console.debug(
90
- "Connection with client successfully established",
91
- this.allowedActions
92
- )
93
- if (!this.isTestingEnvironment) {
94
- this.log("ready", ["Ready to compile or test..."])
95
- }
96
-
97
- socket.on(
98
- "compiler",
99
- ({ action, data }: { action: string; data: IExerciseData }) => {
100
- this.emit("clean", "pending", ["Working..."])
101
- if (typeof data.exerciseSlug === "undefined") {
102
- this.log("internal-error", ["No exercise slug specified"])
103
- Console.error("No exercise slug especified")
104
- return
105
- }
106
-
107
- if (
108
- this.actionCallBacks &&
109
- typeof this.actionCallBacks[action] === "function"
110
- ) {
111
- this.actionCallBacks[action](data)
112
- } else {
113
- this.log("internal-error", ["Uknown action " + action])
114
- }
115
- }
116
- )
117
- })
118
- }
119
- },
120
- on: function (action: TAction, callBack: ICallback) {
121
- if (this.actionCallBacks) {
122
- this.actionCallBacks[action] = callBack
123
- }
124
- },
125
- clean: function (_ = "pending", logs = []) {
126
- this.emit("clean", "pending", logs)
127
- },
128
- ask: function (questions = []) {
129
- return new Promise((resolve, _) => {
130
- this.emit("ask", "pending", ["Waiting for input..."], questions)
131
-
132
- this.on("input", ({ inputs }: any) => {
133
- // Workaround to fix issue because null inputs
134
-
135
- let isNull = false
136
- // eslint-disable-next-line
137
- inputs.forEach((input: any) => {
138
- if (input === null) {
139
- isNull = true
140
- }
141
- })
142
-
143
- if (!isNull) {
144
- resolve(inputs)
145
- }
146
- })
147
- })
148
- },
149
- sessionRefreshed: function (data) {
150
- this.emit("session-refreshed", "", [data])
151
- },
152
-
153
- reload: function (
154
- files: Array<string> | null = null,
155
- exercises: Array<string> | null = null
156
- ) {
157
- this.emit("reload", files?.join("") || "", exercises!)
158
- },
159
- openWindow: function (url = "") {
160
- queue.dispatcher().enqueue(queue.events.OPEN_WINDOW, url)
161
- this.emit(
162
- queue.events.OPEN_WINDOW as TAction,
163
- "ready",
164
- [`Opening ${url}`],
165
- [],
166
- [],
167
- url
168
- )
169
- },
170
- log: function (
171
- status: TStatus,
172
- messages: string | Array<string> = [],
173
- report: Array<string> = [],
174
- data: any = null
175
- ) {
176
- this.emit("log", status, messages, [], report, data)
177
- Console.log(messages)
178
- },
179
- emit: function (
180
- action: TAction,
181
- status: TStatus | string = "ready",
182
- logs: string | Array<string> = [],
183
- inputs: Array<string> = [],
184
- report: Array<string> = [],
185
- data: any = null
186
- ) {
187
- if (
188
- this.config?.compiler &&
189
- ["webpack", "vanillajs", "vue", "react", "css", "html"].includes(
190
- this.config?.compiler
191
- )
192
- ) {
193
- if (["compiler-success", "compiler-warning"].includes(status))
194
- this.addAllowed("preview")
195
- if (["compiler-error"].includes(status) || action === "ready")
196
- this.removeAllowed("preview")
197
- }
198
-
199
- if (this.config?.grading === "incremental") {
200
- this.removeAllowed("reset")
201
- }
202
-
203
- // eslint-disable-next-line
204
- this.config?.disabledActions?.forEach((a) => this.removeAllowed(a))
205
-
206
- this.socket?.emit("compiler", {
207
- action,
208
- status,
209
- logs,
210
- allowed: this.allowedActions,
211
- inputs,
212
- report,
213
- data,
214
- })
215
- },
216
-
217
- ready: function (message: string) {
218
- this.log("ready", [message])
219
- },
220
- success: function (type: TSuccessType, stdout: string, lang) {
221
- const types = ["compiler", "testing"]
222
-
223
- if (!types.includes(type))
224
- this.fatal(`Invalid socket success type "${type}" on socket`)
225
- else if (
226
- stdout === "" &&
227
- lang &&
228
- Object.keys(languageToMessage).includes(lang)
229
- ) {
230
- this.log((type + "-success") as TSuccessType, [languageToMessage[lang]])
231
- } else if (stdout === "") {
232
- this.log((type + "-success") as TSuccessType, [
233
- "No stdout to display on the console",
234
- ])
235
- } else
236
- this.log((type + "-success") as TSuccessType, [stdout])
237
- },
238
- error: function (type: TStatus, stdout: string) {
239
- if (!this.config?.editor.hideTerminal) {
240
- queue.dispatcher().enqueue(queue.events.OPEN_TERMINAL, "")
241
- }
242
-
243
- this.log(type, [stdout])
244
-
245
- if (this.isTestingEnvironment) {
246
- this.onTestingFinished({
247
- result: "failed",
248
- })
249
- }
250
- },
251
- complete: function () {
252
- console.log("complete")
253
- },
254
- dialog: function (message: string, format = "md") {
255
- if (!this.socket) {
256
- this.fatal("Socket is not initialized")
257
- return
258
- }
259
-
260
- this.emit("dialog", "talk", [], undefined, undefined, { message, format })
261
- },
262
-
263
- fatal: function (msg: string) {
264
- this.log("internal-error", [msg])
265
- throw msg
266
- },
267
- onTestingFinished: function (result: any) {
268
- if (this.config?.testingFinishedCallback) {
269
- this.config.testingFinishedCallback(result)
270
- }
271
- },
272
- }
273
-
274
- export default SocketManager
1
+ import { Socket, Server } from "socket.io"
2
+ import Console from "../utils/console"
3
+ import queue from "../utils/fileQueue"
4
+
5
+ import { ISocket, TPossibleActions } from "../models/socket"
6
+ import { IConfig } from "../models/config"
7
+ import { ICallback, TAction } from "../models/action"
8
+ import { IExercise, IExerciseData } from "../models/exercise-obj"
9
+ import { TStatus } from "../models/status"
10
+ import { TSuccessType } from "../models/success-types"
11
+ import * as http from "http"
12
+
13
+ const languageToMessage: Record<string, string> = {
14
+ python3:
15
+ "Your code executed without erros, but no output is shown on the terminal. Maybe you forgot to include a print statement in your code?",
16
+ node: "Your code executed without erros, but no output is shown on the terminal. Maybe you forgot to include a console.log statement in your code?",
17
+ }
18
+
19
+ const SocketManager: ISocket = {
20
+ socket: null,
21
+ config: null,
22
+ allowedActions: [],
23
+ possibleActions: ["build", "reset", "test", "tutorial"],
24
+ isTestingEnvironment: false,
25
+ actionCallBacks: {
26
+ clean: (_, s: { logs: Array<string> }) => {
27
+ s.logs = []
28
+ },
29
+ },
30
+ addAllowed: function (actions: Array<TPossibleActions> | TPossibleActions) {
31
+ if (!Array.isArray(actions))
32
+ actions = [actions]
33
+
34
+ // avoid adding the "test" action if grading is disabled
35
+ if (
36
+ actions.includes("test") &&
37
+ this.config?.disabledActions?.includes("test")
38
+ ) {
39
+ actions = actions.filter((a: TPossibleActions) => a !== "test")
40
+ }
41
+
42
+ this.allowedActions = [
43
+ ...(this.allowedActions || []).filter(
44
+ (a: TPossibleActions) => !actions.includes(a)
45
+ ),
46
+ ...actions,
47
+ ]
48
+ },
49
+ removeAllowed: function (
50
+ actions: Array<TPossibleActions> | TPossibleActions
51
+ ) {
52
+ if (!Array.isArray(actions)) {
53
+ actions = [actions]
54
+ }
55
+
56
+ this.allowedActions = (this.allowedActions || []).filter(
57
+ (a: TPossibleActions) => !actions.includes(a)
58
+ )
59
+ },
60
+ start: function (
61
+ config: IConfig,
62
+ server: http.Server,
63
+ isTestingEnvironment = false
64
+ ) {
65
+ this.config = config
66
+ this.isTestingEnvironment = isTestingEnvironment
67
+ this.socket = new Server(server, {
68
+ allowEIO3: true,
69
+ cors: {
70
+ origin: "http://localhost:5173",
71
+ methods: ["GET", "POST"],
72
+ },
73
+ })
74
+
75
+ this.allowedActions =
76
+ this.config?.disabledActions?.includes("test") ||
77
+ this.config?.disableGrading ?
78
+ this.possibleActions.filter(
79
+ a => !this.config?.disabledActions?.includes(a) && a !== "test"
80
+ ) :
81
+ this.possibleActions.filter(a => !this.allowedActions?.includes(a))
82
+
83
+ if (this.config?.grading === "incremental") {
84
+ this.removeAllowed("reset")
85
+ }
86
+
87
+ if (this.socket) {
88
+ this.socket.on("connection", (socket: Socket) => {
89
+ Console.debug(
90
+ "Connection with client successfully established",
91
+ this.allowedActions
92
+ )
93
+ if (!this.isTestingEnvironment) {
94
+ this.log("ready", ["Ready to compile or test..."])
95
+ }
96
+
97
+ socket.on(
98
+ "compiler",
99
+ ({ action, data }: { action: string; data: IExerciseData }) => {
100
+ this.emit("clean", "pending", ["Working..."])
101
+ if (typeof data.exerciseSlug === "undefined") {
102
+ this.log("internal-error", ["No exercise slug specified"])
103
+ Console.error("No exercise slug especified")
104
+ return
105
+ }
106
+
107
+ if (
108
+ this.actionCallBacks &&
109
+ typeof this.actionCallBacks[action] === "function"
110
+ ) {
111
+ this.actionCallBacks[action](data)
112
+ } else {
113
+ this.log("internal-error", ["Uknown action " + action])
114
+ }
115
+ }
116
+ )
117
+ })
118
+ }
119
+ },
120
+ on: function (action: TAction, callBack: ICallback) {
121
+ if (this.actionCallBacks) {
122
+ this.actionCallBacks[action] = callBack
123
+ }
124
+ },
125
+ clean: function (_ = "pending", logs = []) {
126
+ this.emit("clean", "pending", logs)
127
+ },
128
+ ask: function (questions = []) {
129
+ return new Promise((resolve, _) => {
130
+ this.emit("ask", "pending", ["Waiting for input..."], questions)
131
+
132
+ this.on("input", ({ inputs }: any) => {
133
+ // Workaround to fix issue because null inputs
134
+
135
+ let isNull = false
136
+ // eslint-disable-next-line
137
+ inputs.forEach((input: any) => {
138
+ if (input === null) {
139
+ isNull = true
140
+ }
141
+ })
142
+
143
+ if (!isNull) {
144
+ resolve(inputs)
145
+ }
146
+ })
147
+ })
148
+ },
149
+ sessionRefreshed: function (data) {
150
+ this.emit("session-refreshed", "", [data])
151
+ },
152
+
153
+ reload: function (
154
+ files: Array<string> | null = null,
155
+ exercises: Array<string> | null = null
156
+ ) {
157
+ this.emit("reload", files?.join("") || "", exercises!)
158
+ },
159
+ openWindow: function (url = "") {
160
+ queue.dispatcher().enqueue(queue.events.OPEN_WINDOW, url)
161
+ this.emit(
162
+ queue.events.OPEN_WINDOW as TAction,
163
+ "ready",
164
+ [`Opening ${url}`],
165
+ [],
166
+ [],
167
+ url
168
+ )
169
+ },
170
+ log: function (
171
+ status: TStatus,
172
+ messages: string | Array<string> = [],
173
+ report: Array<string> = [],
174
+ data: any = null
175
+ ) {
176
+ this.emit("log", status, messages, [], report, data)
177
+ Console.log(messages)
178
+ },
179
+ emit: function (
180
+ action: TAction,
181
+ status: TStatus | string = "ready",
182
+ logs: string | Array<string> = [],
183
+ inputs: Array<string> = [],
184
+ report: Array<string> = [],
185
+ data: any = null
186
+ ) {
187
+ if (
188
+ this.config?.compiler &&
189
+ ["webpack", "vanillajs", "vue", "react", "css", "html"].includes(
190
+ this.config?.compiler
191
+ )
192
+ ) {
193
+ if (["compiler-success", "compiler-warning"].includes(status))
194
+ this.addAllowed("preview")
195
+ if (["compiler-error"].includes(status) || action === "ready")
196
+ this.removeAllowed("preview")
197
+ }
198
+
199
+ if (this.config?.grading === "incremental") {
200
+ this.removeAllowed("reset")
201
+ }
202
+
203
+ // eslint-disable-next-line
204
+ this.config?.disabledActions?.forEach((a) => this.removeAllowed(a))
205
+
206
+ this.socket?.emit("compiler", {
207
+ action,
208
+ status,
209
+ logs,
210
+ allowed: this.allowedActions,
211
+ inputs,
212
+ report,
213
+ data,
214
+ })
215
+ },
216
+
217
+ ready: function (message: string) {
218
+ this.log("ready", [message])
219
+ },
220
+ success: function (type: TSuccessType, stdout: string, lang) {
221
+ const types = ["compiler", "testing"]
222
+
223
+ if (!types.includes(type))
224
+ this.fatal(`Invalid socket success type "${type}" on socket`)
225
+ else if (
226
+ stdout === "" &&
227
+ lang &&
228
+ Object.keys(languageToMessage).includes(lang)
229
+ ) {
230
+ this.log((type + "-success") as TSuccessType, [languageToMessage[lang]])
231
+ } else if (stdout === "") {
232
+ this.log((type + "-success") as TSuccessType, [
233
+ "No stdout to display on the console",
234
+ ])
235
+ } else
236
+ this.log((type + "-success") as TSuccessType, [stdout])
237
+ },
238
+ error: function (type: TStatus, stdout: string) {
239
+ if (!this.config?.editor.hideTerminal) {
240
+ queue.dispatcher().enqueue(queue.events.OPEN_TERMINAL, "")
241
+ }
242
+
243
+ this.log(type, [stdout])
244
+
245
+ if (this.isTestingEnvironment) {
246
+ this.onTestingFinished({
247
+ result: "failed",
248
+ })
249
+ }
250
+ },
251
+ complete: function () {
252
+ console.log("complete")
253
+ },
254
+ dialog: function (message: string, format = "md") {
255
+ if (!this.socket) {
256
+ this.fatal("Socket is not initialized")
257
+ return
258
+ }
259
+
260
+ this.emit("dialog", "talk", [], undefined, undefined, { message, format })
261
+ },
262
+
263
+ fatal: function (msg: string) {
264
+ this.log("internal-error", [msg])
265
+ throw msg
266
+ },
267
+ onTestingFinished: function (result: any) {
268
+ if (this.config?.testingFinishedCallback) {
269
+ this.config.testingFinishedCallback(result)
270
+ }
271
+ },
272
+ }
273
+
274
+ export default SocketManager