@learnpack/learnpack 5.0.335 → 5.0.339

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 (48) hide show
  1. package/bin/run +17 -17
  2. package/lib/commands/init.js +41 -41
  3. package/lib/commands/serve.js +589 -126
  4. package/lib/creatorDist/assets/index-BhqDgBS9.js +8448 -78631
  5. package/lib/creatorDist/assets/index-CjddKHB_.css +1 -1688
  6. package/lib/managers/config/exercise.js +2 -14
  7. package/lib/managers/readmeHistoryService.js +3 -1
  8. package/lib/managers/server/routes.js +2 -1
  9. package/lib/utils/configBuilder.js +2 -1
  10. package/lib/utils/creatorUtilities.js +14 -14
  11. package/lib/utils/exerciseFileOrder.d.ts +20 -0
  12. package/lib/utils/exerciseFileOrder.js +49 -0
  13. package/lib/utils/export/epub.js +26 -26
  14. package/lib/utils/readmeSanitizer.d.ts +8 -0
  15. package/lib/utils/readmeSanitizer.js +13 -0
  16. package/lib/utils/templates/epub/epub.css +146 -146
  17. package/lib/utils/templates/scorm/config/api.js +175 -175
  18. package/package.json +1 -1
  19. package/src/commands/init.ts +655 -655
  20. package/src/commands/publish.ts +670 -670
  21. package/src/commands/serve.ts +5853 -5216
  22. package/src/creator/eslint.config.js +28 -28
  23. package/src/creator/src/index.css +227 -227
  24. package/src/creator/src/utils/lib.ts +471 -471
  25. package/src/creatorDist/assets/index-BhqDgBS9.js +8448 -78631
  26. package/src/creatorDist/assets/index-CjddKHB_.css +1 -1688
  27. package/src/managers/config/exercise.ts +3 -15
  28. package/src/managers/readmeHistoryService.ts +3 -1
  29. package/src/managers/server/routes.ts +15 -6
  30. package/src/managers/session.ts +184 -184
  31. package/src/ui/_app/app.css +1 -1
  32. package/src/ui/_app/app.js +1950 -1878
  33. package/src/ui/app.tar.gz +0 -0
  34. package/src/utils/api.ts +675 -675
  35. package/src/utils/configBuilder.ts +102 -100
  36. package/src/utils/creatorUtilities.ts +536 -536
  37. package/src/utils/errors.ts +108 -108
  38. package/src/utils/exerciseFileOrder.ts +50 -0
  39. package/src/utils/export/epub.ts +553 -553
  40. package/src/utils/export/index.ts +4 -4
  41. package/src/utils/export/scorm.ts +121 -121
  42. package/src/utils/export/shared.ts +61 -61
  43. package/src/utils/export/types.ts +25 -25
  44. package/src/utils/export/zip.ts +55 -55
  45. package/src/utils/readmeSanitizer.ts +10 -0
  46. package/src/utils/rigoActions.ts +642 -642
  47. package/src/utils/templates/epub/epub.css +146 -146
  48. package/src/utils/templates/scorm/config/api.js +175 -175
@@ -7,6 +7,7 @@ import allowed from "./allowed_files"
7
7
  import { IConfigObj } from "../../models/config"
8
8
  import { IFile } from "../../models/file"
9
9
  import { IExercise } from "../../models/exercise-obj"
10
+ import { compareExerciseFileNames } from "../../utils/exerciseFileOrder"
10
11
 
11
12
  // function processQuestions(markdown: string): {
12
13
  // updatedMarkdown: string
@@ -46,7 +47,7 @@ import { IExercise } from "../../models/exercise-obj"
46
47
  // return { updatedMarkdown, questions }
47
48
  // }
48
49
  // eslint-disable-next-line
49
- const frontMatter = require("front-matter")
50
+ const frontMatter = require("front-matter");
50
51
 
51
52
  export const exercise = (
52
53
  path: string,
@@ -342,20 +343,7 @@ export const filterFiles = (files: Array<string>, basePath = ".") =>
342
343
  path: basePath + "/" + ex,
343
344
  } as IFile),
344
345
  }))
345
- .sort((f1, f2) => {
346
- const score: { [key: string]: number } = {
347
- // sorting priority
348
- "index.html": 1,
349
- "styles.css": 2,
350
- "styles.scss": 2,
351
- "style.css": 2,
352
- "style.scss": 2,
353
- "index.css": 2,
354
- "index.scss": 2,
355
- "index.js": 3,
356
- }
357
- return score[f1.name] < score[f2.name] ? -1 : 1
358
- })
346
+ .sort((f1, f2) => compareExerciseFileNames(f1.name, f2.name))
359
347
 
360
348
  export default {
361
349
  exercise,
@@ -1,5 +1,6 @@
1
1
  import type { Bucket } from "@google-cloud/storage"
2
2
  import type { HistoryManager } from "./historyManager"
3
+ import { sanitizeReadmeNewlines } from "../utils/readmeSanitizer"
3
4
 
4
5
  export interface SaveReadmeWithHistoryOptions {
5
6
  courseSlug: string;
@@ -260,10 +261,11 @@ export class ReadmeHistoryService {
260
261
  fileName: string,
261
262
  content: string
262
263
  ): Promise<void> {
264
+ const sanitizedContent = sanitizeReadmeNewlines(content)
263
265
  const filePath = `courses/${courseSlug}/exercises/${exerciseSlug}/${fileName}`
264
266
  const file = this.bucket.file(filePath)
265
267
 
266
- await file.save(content, {
268
+ await file.save(sanitizedContent, {
267
269
  resumable: false,
268
270
  })
269
271
  }
@@ -14,8 +14,9 @@ import SessionManager from "../../managers/session"
14
14
  import TelemetryManager from "../telemetry"
15
15
  import { saveTranslatedReadme } from "../../utils/creatorUtilities"
16
16
  import { translateExercise } from "../../utils/rigoActions"
17
+ import { sanitizeReadmeNewlines } from "../../utils/readmeSanitizer"
17
18
  import { addExerciseToSidebar } from "../../utils/sidebarGenerator"
18
- import path = require("path")
19
+ import path = require("path");
19
20
  // import { eventManager } from "../../utils/osOperations"
20
21
 
21
22
  const withHandler =
@@ -204,7 +205,8 @@ export default async function (
204
205
  // symbolic link to maintain path compatiblity
205
206
  const fetchStaticAsset = withHandler((req, res) => {
206
207
  const filePath = `${config?.dirPath}/assets/${req.params.filePath}`
207
- if (!fs.existsSync(filePath)) throw new Error("File not found: " + filePath)
208
+ if (!fs.existsSync(filePath))
209
+ throw new Error("File not found: " + filePath)
208
210
  const content = fs.readFileSync(filePath)
209
211
  res.write(content)
210
212
  res.end()
@@ -271,7 +273,11 @@ export default async function (
271
273
  const exercise = configManager.getExercise(req.params.slug)
272
274
  res.json(exercise)
273
275
  if (exercise.position) {
274
- TelemetryManager.registerStepEvent(exercise.position, "open_step", {})
276
+ TelemetryManager.registerStepEvent(
277
+ exercise.position,
278
+ "open_step",
279
+ {}
280
+ )
275
281
  }
276
282
 
277
283
  return
@@ -286,7 +292,7 @@ export default async function (
286
292
  dispatcher.enqueue(dispatcher.events.START_EXERCISE, req.params.slug)
287
293
  }
288
294
 
289
- type TEntry = "python3" | "html" | "node" | "react" | "java"
295
+ type TEntry = "python3" | "html" | "node" | "react" | "java";
290
296
 
291
297
  const entries = new Set(
292
298
  Object.keys(config?.entries!).map(
@@ -438,7 +444,7 @@ export default async function (
438
444
  const response = await translateExercise(
439
445
  rigoToken,
440
446
  {
441
- text_to_translate: readme.body,
447
+ text_to_translate: sanitizeReadmeNewlines(readme.body),
442
448
  output_language: language,
443
449
  },
444
450
  `${process.env.HOST}/webhooks/translate-exercise`
@@ -550,5 +556,8 @@ export default async function (
550
556
 
551
557
  app.use("/", express.static(`${config?.dirPath}/_app`))
552
558
 
553
- app.use("/creator", express.static(path.join(__dirname, "..", "creatorDist")))
559
+ app.use(
560
+ "/creator",
561
+ express.static(path.join(__dirname, "..", "creatorDist"))
562
+ )
554
563
  }
@@ -1,184 +1,184 @@
1
- import Console from "../utils/console"
2
- import api from "../utils/api"
3
-
4
- import v from "validator"
5
- import { ValidationError, InternalError } from "../utils/errors"
6
-
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
- import TelemetryManager from "./telemetry"
14
- import * as path from "path"
15
- import * as os from "os"
16
-
17
- const GLOBAL_SESSION_PATH = path.join(os.homedir(), ".learnpack-session")
18
-
19
- const Session: ISession = {
20
- sessionStarted: false,
21
- token: null,
22
- config: null,
23
- currentCohort: null,
24
- initialize: async function () {
25
- if (!this.sessionStarted) {
26
- // Use a system-wide path instead of config.dirPath
27
- if (!fs.existsSync(GLOBAL_SESSION_PATH)) {
28
- fs.mkdirSync(GLOBAL_SESSION_PATH, { recursive: true })
29
- }
30
-
31
- await storage.init({ dir: GLOBAL_SESSION_PATH })
32
- this.sessionStarted = true
33
- }
34
-
35
- return true
36
- },
37
-
38
- setRigoToken: async function (token: string) {
39
- await this.initialize()
40
- const payload = await storage.getItem("bc-payload")
41
- await storage.setItem("bc-payload", {
42
- ...payload,
43
- rigobot: { key: token },
44
- })
45
- Console.debug("Rigobot token successfuly set")
46
- return true
47
- },
48
- setTabHash: async function (hash: string) {
49
- await this.initialize()
50
- const payload = await storage.getItem("bc-payload")
51
- await storage.setItem("bc-payload", {
52
- ...payload,
53
- tabHash: hash,
54
- })
55
- Console.debug("tabHash successfuly set")
56
- return true
57
- },
58
- setSessionKey: async function (value: string) {
59
- await this.initialize()
60
- const payload = await storage.getItem("bc-payload")
61
- await storage.setItem("bc-payload", {
62
- ...payload,
63
- sessionKey: value,
64
- })
65
- Console.debug("sessionKey successfuly set")
66
- return true
67
- },
68
- setPayload: async function (value: IPayload) {
69
- await this.initialize()
70
- await storage.setItem("bc-payload", { ...value })
71
- Console.debug(
72
- "Payload successfuly found and set for user " + value.user_id
73
- )
74
-
75
- // TelemetryManager.setStudent({
76
- // user_id: value.user_id.toString(),
77
- // email: value.email,
78
- // token: value.token,
79
- // })
80
-
81
- return true
82
- },
83
- getPayload: async function () {
84
- await this.initialize()
85
- let payload = null
86
- try {
87
- payload = await storage.getItem("bc-payload")
88
- } catch {
89
- Console.debug("Error retriving session payload")
90
- }
91
-
92
- return payload
93
- },
94
- isActive: function () {
95
- return !!this.token
96
- },
97
- get: async function (configObj?: IConfigObj) {
98
- if (configObj && configObj.config) {
99
- this.config = configObj.config
100
- }
101
-
102
- await this.sync()
103
- if (!this.isActive()) {
104
- return null
105
- }
106
-
107
- const payload = await this.getPayload()
108
-
109
- return {
110
- payload,
111
- token: this.token,
112
- }
113
- },
114
- login: async function () {
115
- const email = await cli.prompt("What is your email?")
116
- if (!v.isEmail(email)) {
117
- throw ValidationError("Invalid email")
118
- }
119
-
120
- const password = await cli.prompt("What is your password?", {
121
- type: "hide",
122
- })
123
-
124
- const data = await api.login(email, password)
125
-
126
- if (data) {
127
- Console.debug("Login successfull")
128
- // cli.log(data)
129
- await this.start({ token: data.token, payload: data })
130
- return data
131
- }
132
- },
133
- loginWeb: async function (email, password) {
134
- if (!v.isEmail(email)) {
135
- throw ValidationError("Invalid email")
136
- }
137
-
138
- const data = await api.login(email, password)
139
- if (data) {
140
- this.start({ token: data.token, payload: data })
141
-
142
- return data
143
- }
144
- },
145
- sync: async function () {
146
- const payload = await this.getPayload()
147
- if (payload) {
148
- this.token = payload.token
149
- }
150
- },
151
- start: async function ({ token, payload = null }: IStartProps) {
152
- if (!token) {
153
- throw new Error("A token and email is needed to start a session")
154
- }
155
-
156
- this.token = token
157
-
158
- if (payload && (await this.setPayload(payload))) {
159
- Console.success(`Successfully logged in as user ${payload.user_id}`)
160
- }
161
- },
162
- destroy: async function () {
163
- if (!this.sessionStarted) {
164
- await this.initialize()
165
- }
166
-
167
- await storage.clear()
168
- this.token = null
169
- Console.success("You have logged out")
170
- },
171
- breakToken: async function () {
172
- const payload = await this.getPayload()
173
- if (payload) {
174
- this.token = "asdasdasdasd"
175
- await storage.setItem("bc-payload", {
176
- ...payload,
177
- token: "asdasdsad",
178
- })
179
- Console.success("Token broken successfully")
180
- }
181
- },
182
- }
183
-
184
- export default Session
1
+ import Console from "../utils/console"
2
+ import api from "../utils/api"
3
+
4
+ import v from "validator"
5
+ import { ValidationError, InternalError } from "../utils/errors"
6
+
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
+ import TelemetryManager from "./telemetry"
14
+ import * as path from "path"
15
+ import * as os from "os"
16
+
17
+ const GLOBAL_SESSION_PATH = path.join(os.homedir(), ".learnpack-session")
18
+
19
+ const Session: ISession = {
20
+ sessionStarted: false,
21
+ token: null,
22
+ config: null,
23
+ currentCohort: null,
24
+ initialize: async function () {
25
+ if (!this.sessionStarted) {
26
+ // Use a system-wide path instead of config.dirPath
27
+ if (!fs.existsSync(GLOBAL_SESSION_PATH)) {
28
+ fs.mkdirSync(GLOBAL_SESSION_PATH, { recursive: true })
29
+ }
30
+
31
+ await storage.init({ dir: GLOBAL_SESSION_PATH })
32
+ this.sessionStarted = true
33
+ }
34
+
35
+ return true
36
+ },
37
+
38
+ setRigoToken: async function (token: string) {
39
+ await this.initialize()
40
+ const payload = await storage.getItem("bc-payload")
41
+ await storage.setItem("bc-payload", {
42
+ ...payload,
43
+ rigobot: { key: token },
44
+ })
45
+ Console.debug("Rigobot token successfuly set")
46
+ return true
47
+ },
48
+ setTabHash: async function (hash: string) {
49
+ await this.initialize()
50
+ const payload = await storage.getItem("bc-payload")
51
+ await storage.setItem("bc-payload", {
52
+ ...payload,
53
+ tabHash: hash,
54
+ })
55
+ Console.debug("tabHash successfuly set")
56
+ return true
57
+ },
58
+ setSessionKey: async function (value: string) {
59
+ await this.initialize()
60
+ const payload = await storage.getItem("bc-payload")
61
+ await storage.setItem("bc-payload", {
62
+ ...payload,
63
+ sessionKey: value,
64
+ })
65
+ Console.debug("sessionKey successfuly set")
66
+ return true
67
+ },
68
+ setPayload: async function (value: IPayload) {
69
+ await this.initialize()
70
+ await storage.setItem("bc-payload", { ...value })
71
+ Console.debug(
72
+ "Payload successfuly found and set for user " + value.user_id
73
+ )
74
+
75
+ // TelemetryManager.setStudent({
76
+ // user_id: value.user_id.toString(),
77
+ // email: value.email,
78
+ // token: value.token,
79
+ // })
80
+
81
+ return true
82
+ },
83
+ getPayload: async function () {
84
+ await this.initialize()
85
+ let payload = null
86
+ try {
87
+ payload = await storage.getItem("bc-payload")
88
+ } catch {
89
+ Console.debug("Error retriving session payload")
90
+ }
91
+
92
+ return payload
93
+ },
94
+ isActive: function () {
95
+ return !!this.token
96
+ },
97
+ get: async function (configObj?: IConfigObj) {
98
+ if (configObj && configObj.config) {
99
+ this.config = configObj.config
100
+ }
101
+
102
+ await this.sync()
103
+ if (!this.isActive()) {
104
+ return null
105
+ }
106
+
107
+ const payload = await this.getPayload()
108
+
109
+ return {
110
+ payload,
111
+ token: this.token,
112
+ }
113
+ },
114
+ login: async function () {
115
+ const email = await cli.prompt("What is your email?")
116
+ if (!v.isEmail(email)) {
117
+ throw ValidationError("Invalid email")
118
+ }
119
+
120
+ const password = await cli.prompt("What is your password?", {
121
+ type: "hide",
122
+ })
123
+
124
+ const data = await api.login(email, password)
125
+
126
+ if (data) {
127
+ Console.debug("Login successfull")
128
+ // cli.log(data)
129
+ await this.start({ token: data.token, payload: data })
130
+ return data
131
+ }
132
+ },
133
+ loginWeb: async function (email, password) {
134
+ if (!v.isEmail(email)) {
135
+ throw ValidationError("Invalid email")
136
+ }
137
+
138
+ const data = await api.login(email, password)
139
+ if (data) {
140
+ this.start({ token: data.token, payload: data })
141
+
142
+ return data
143
+ }
144
+ },
145
+ sync: async function () {
146
+ const payload = await this.getPayload()
147
+ if (payload) {
148
+ this.token = payload.token
149
+ }
150
+ },
151
+ start: async function ({ token, payload = null }: IStartProps) {
152
+ if (!token) {
153
+ throw new Error("A token and email is needed to start a session")
154
+ }
155
+
156
+ this.token = token
157
+
158
+ if (payload && (await this.setPayload(payload))) {
159
+ Console.success(`Successfully logged in as user ${payload.user_id}`)
160
+ }
161
+ },
162
+ destroy: async function () {
163
+ if (!this.sessionStarted) {
164
+ await this.initialize()
165
+ }
166
+
167
+ await storage.clear()
168
+ this.token = null
169
+ Console.success("You have logged out")
170
+ },
171
+ breakToken: async function () {
172
+ const payload = await this.getPayload()
173
+ if (payload) {
174
+ this.token = "asdasdasdasd"
175
+ await storage.setItem("bc-payload", {
176
+ ...payload,
177
+ token: "asdasdsad",
178
+ })
179
+ Console.success("Token broken successfully")
180
+ }
181
+ },
182
+ }
183
+
184
+ export default Session