@learnpack/learnpack 5.0.29 → 5.0.31

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.
@@ -123,7 +123,7 @@ export const download = (url: string, dest: string) => {
123
123
  })
124
124
  file.on("error", err => {
125
125
  file.close()
126
- if (err.code === "EEXIST") {
126
+ if (err.name === "EEXIST") {
127
127
  Console.debug("File already exists")
128
128
  resolve("File already exists")
129
129
  } else {
@@ -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) =>
@@ -381,6 +383,106 @@ throw new Error("File not found: " + filePath)
381
383
  })
382
384
  )
383
385
 
386
+ app.delete(
387
+ "/exercise/:slug/delete",
388
+ withHandler(async (req: express.Request, res: express.Response) => {
389
+ const exerciseDeleted = configManager.deleteExercise(req.params.slug)
390
+ if (exerciseDeleted) {
391
+ configManager.buildIndex()
392
+ res.json({ status: "ok" })
393
+ } else {
394
+ res.status(500).json({ error: "Failed to delete exercise" })
395
+ }
396
+ })
397
+ )
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
+
457
+ app.post(
458
+ "/exercise/:slug/create",
459
+ jsonBodyParser,
460
+ withHandler(async (req: express.Request, res: express.Response) => {
461
+ const { title, readme, language } = req.body
462
+ const { slug } = req.params
463
+
464
+ if (!title || !readme || !language) {
465
+ return res.status(400).json({ error: "Missing required fields" })
466
+ }
467
+
468
+ try {
469
+ const exerciseCreated = await configManager.createExercise(
470
+ slug,
471
+ readme,
472
+ language
473
+ )
474
+ if (exerciseCreated) {
475
+ configManager.buildIndex()
476
+ res.json({ status: "ok" })
477
+ } else {
478
+ res.status(500).json({ error: "Failed to create exercise" })
479
+ }
480
+ } catch {
481
+ res.status(500).json({ error: "Failed to create exercise" })
482
+ }
483
+ })
484
+ )
485
+
384
486
  const textBodyParser = bodyParser.text()
385
487
  app.put(
386
488
  "/exercise/:slug/file/:fileName",
@@ -163,6 +163,10 @@ const Session: ISession = {
163
163
  }
164
164
  },
165
165
  destroy: async function () {
166
+ if (!this.sessionStarted) {
167
+ await this.initialize()
168
+ }
169
+
166
170
  await storage.clear()
167
171
  this.token = null
168
172
  Console.success("You have logged out")
@@ -1,23 +1,25 @@
1
- import { IConfigObj, TGrading } from "./config"
2
- import { IExercise } from "./exercise-obj"
3
-
4
- export interface IConfigManagerAttributes {
5
- grading: TGrading;
6
- disableGrading: boolean;
7
- version: string;
8
- mode?: string;
9
- }
10
-
11
- export interface IConfigManager {
12
- validLanguages?: any;
13
- get: () => IConfigObj;
14
- clean: () => void;
15
- getExercise: (slug: string | undefined) => IExercise;
16
- startExercise: (slug: string) => IExercise;
17
- reset: (slug: string) => void;
18
- buildIndex: () => boolean | void;
19
- watchIndex: (onChange: (...args: Array<any>) => void) => void;
20
- save: () => void;
21
- noCurrentExercise: () => void;
22
- getAllExercises: () => IExercise[];
23
- }
1
+ import { IConfigObj, TGrading } from "./config"
2
+ import { IExercise } from "./exercise-obj"
3
+
4
+ export interface IConfigManagerAttributes {
5
+ grading: TGrading
6
+ disableGrading: boolean
7
+ version: string
8
+ mode?: string
9
+ }
10
+
11
+ export interface IConfigManager {
12
+ validLanguages?: any
13
+ get: () => IConfigObj
14
+ clean: () => void
15
+ getExercise: (slug: string | undefined) => IExercise
16
+ startExercise: (slug: string) => IExercise
17
+ reset: (slug: string) => void
18
+ createExercise: (slug: string, content: string, language: string) => boolean
19
+ deleteExercise: (slug: string) => boolean
20
+ buildIndex: () => boolean | void
21
+ watchIndex: (onChange: (...args: Array<any>) => void) => void
22
+ save: () => void
23
+ noCurrentExercise: () => void
24
+ getAllExercises: () => IExercise[]
25
+ }
@@ -1,24 +1,24 @@
1
- import * as chalk from "chalk"
2
-
3
- export default {
4
- // _debug: true,
5
- _debug: process.env.DEBUG === "true",
6
- startDebug: function () {
7
- this._debug = true
8
- },
9
- log: (msg: string | Array<string>, ...args: Array<any>) =>
10
- console.log(chalk.gray(msg), ...args),
11
- error: (msg: string, ...args: Array<any>) =>
12
- console.log(chalk.red("⨉ " + msg), ...args),
13
- success: (msg: string, ...args: Array<any>) =>
14
- console.log(chalk.green("✓ " + msg), ...args),
15
- info: (msg: any, ...args: Array<any>) =>
16
- console.log(chalk.blue("ⓘ " + msg), ...args),
17
- help: (msg: string) =>
18
- console.log(`${chalk.white.bold("⚠ help:")} ${chalk.white(msg)}`),
19
- debug(...args: Array<any>) {
20
- this._debug && console.log(chalk.magentaBright("⚠ debug: "), args)
21
- },
22
- warning: (msg: string) =>
23
- console.log(`${chalk.yellow("⚠ warning:")} ${chalk.yellow(msg)}`),
24
- }
1
+ import * as chalk from "chalk"
2
+
3
+ export default {
4
+ // _debug: true,
5
+ _debug: process.env.DEBUG === "true",
6
+ startDebug: function () {
7
+ this._debug = true
8
+ },
9
+ log: (msg: string | Array<string>, ...args: Array<any>) =>
10
+ console.log(chalk.gray(msg), ...args),
11
+ error: (msg: string, ...args: Array<any>) =>
12
+ console.log(chalk.red("⨉ " + msg), ...args),
13
+ success: (msg: string, ...args: Array<any>) =>
14
+ console.log(chalk.green("✓ " + msg), ...args),
15
+ info: (msg: any, ...args: Array<any>) =>
16
+ console.log(chalk.blue("ⓘ " + msg), ...args),
17
+ help: (msg: string) =>
18
+ console.log(`${chalk.white.bold("⚠ help:")} ${chalk.white(msg)}`),
19
+ debug(...args: Array<any>) {
20
+ this._debug && console.log(chalk.magentaBright("⚠ debug: "), args)
21
+ },
22
+ warning: (msg: string) =>
23
+ console.log(`${chalk.yellow("⚠ warning:")} ${chalk.yellow(msg)}`),
24
+ }
@@ -1,5 +1,6 @@
1
- // eslint-disable-next-line
1
+
2
2
  const frontMatter = require("front-matter")
3
+
3
4
  import * as path from "path"
4
5
  import * as fs from "fs"
5
6
  import { homedir } from "os"
@@ -61,11 +62,13 @@ export function checkReadingTime(
61
62
  exceedsThreshold: boolean
62
63
  minutes: number
63
64
  body: string
65
+ // readingEase: number
64
66
  } {
65
67
  const parsed = frontMatter(markdown)
66
68
 
67
69
  const readingTime = estimateReadingTime(parsed.body, wordsPerMinute)
68
70
 
71
+ // const readingEase = estimateReadingEase(parsed.body)
69
72
  let attributes = parsed.attributes ? parsed.attributes : {}
70
73
 
71
74
  if (typeof parsed.attributes !== "object") {
@@ -75,6 +78,7 @@ export function checkReadingTime(
75
78
  const updatedAttributes = {
76
79
  ...attributes,
77
80
  readingTime,
81
+ // readingEase,
78
82
  }
79
83
 
80
84
  let yamlFrontMatter = ""
@@ -86,6 +90,7 @@ export function checkReadingTime(
86
90
  exceedsThreshold: false,
87
91
  minutes: 0,
88
92
  body: "",
93
+ // readingEase: 0,
89
94
  }
90
95
  }
91
96
 
@@ -97,6 +102,7 @@ export function checkReadingTime(
97
102
  exceedsThreshold: readingTime.minutes > maxMinutes,
98
103
  minutes: readingTime.minutes,
99
104
  body: parsed.body,
105
+ // readingEase: 0,
100
106
  }
101
107
  }
102
108
 
@@ -189,11 +195,13 @@ export function estimateDuration(listOfSteps: string[]): number {
189
195
  const writeFilePromise = promisify(fs.writeFile)
190
196
  const execPromise = promisify(exec)
191
197
 
192
- const example_content = `
193
- 00.1 - Introduction to AI [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
194
- 01.1 - Introduction to Machine Learning [READ: Small introduction to important concepts such as AI, machine learning, and their applications]
195
- 01.2 - Introduction to Deep Learning [QUIZ: Small introduction to important concepts such as AI, machine learning, and their applications]
196
- 02.1 - Test your knowledge [CODE: Code problem to solve]
198
+ const example_content = `Write or paste your table of content below this line, each topic should be defined on a new line, here is an example:
199
+
200
+ Introduction to AI: Explain what is AI and its applications
201
+ Introduction to Machine Learning: Explain what is machine learning and its applications
202
+ What is an AI Model: Explain what is an AI model and its applications
203
+ How to use an AI Model: Different APIs, local models, etc.
204
+ How to build an AI Model: Fine-tuning, data collection, cleaning and more.
197
205
  `
198
206
 
199
207
  export async function createFileOnDesktop() {
@@ -239,3 +247,55 @@ export function getContentIndex() {
239
247
  const content = fs.readFileSync(filePath, "utf8")
240
248
  return content
241
249
  }
250
+
251
+ // export function fleschKincaidReadingEase(text: string): number {
252
+ // const sentences = text.split(/[.!?]/).filter((s) => s.trim().length > 0)
253
+ // const words = text.split(/\s+/).filter((w) => w.trim().length > 0)
254
+ // const totalSyllables = words.reduce((sum, word) => sum + syllable(word), 0)
255
+
256
+ // const ASL = words.length / sentences.length // Average Sentence Length
257
+ // const ASW = totalSyllables / words.length // Average Syllables per Word
258
+
259
+ // return Math.round(206.835 - 1.015 * ASL - 84.6 * ASW)
260
+ // }
261
+
262
+ export function extractTextFromMarkdown(mdContent: string): string {
263
+ let content = mdContent.replace(/!\[.*?]\(.*?\)/g, "")
264
+ content = content.replace(/\[.*?]\(.*?\)/g, "")
265
+ content = content.replace(/`.*?`/g, "")
266
+ content = content.replace(/```[\S\s]*?```/g, "")
267
+
268
+ return content.trim()
269
+ }
270
+
271
+ // export const estimateReadingEase = async (text: string): Promise<number> => {
272
+ // const cleanedText = extractTextFromMarkdown(text)
273
+ // // @ts-ignore
274
+ // const rs = (await import("text-readability")).default;
275
+ // // return fleschKincaidReadingEase(cleanedText)
276
+ // // const score = readability(cleanedText)
277
+ // // console.log(score)
278
+ // // return score.fleschKincaid ?? 0
279
+ // const score = rs.fleschReadingEase(cleanedText)
280
+ // console.log(score, "SCORE FLESCH READING EASE")
281
+ // return score
282
+ // }
283
+
284
+ const cleanReadme = (readme: string) => {
285
+ // Replace <text> and </text> with nothing
286
+ return readme.replace(/<text>/g, "").replace(/<\/text>/g, "")
287
+ }
288
+
289
+ export const saveTranslatedReadme = async (
290
+ exercise: string,
291
+ languageCode: string,
292
+ readme: string
293
+ ) => {
294
+ const readmePath = path.join(
295
+ process.cwd(),
296
+ "exercises",
297
+ exercise,
298
+ `README.${languageCode}.md`
299
+ )
300
+ fs.writeFileSync(readmePath, cleanReadme(readme))
301
+ }
@@ -62,7 +62,7 @@ return false
62
62
  Console.debug("The user is a creator! 🎉")
63
63
  return true
64
64
  } catch (error) {
65
- Console.debug(error)
65
+ Console.error(error as string)
66
66
  return false
67
67
  }
68
68
  }
@@ -323,3 +323,15 @@ export async function reduceReadme(
323
323
  return null
324
324
  }
325
325
  }
326
+
327
+ export const isValidRigoToken = async (rigobotToken: string) => {
328
+ const rigoUrl = `${RIGOBOT_HOST}/v1/auth/token/${rigobotToken}`
329
+ const rigoResp = await fetch(rigoUrl)
330
+ if (!rigoResp.ok) {
331
+ Console.debug("Invalid Rigobot token", rigoResp.status)
332
+ Console.debug(rigoResp.statusText)
333
+ return false
334
+ }
335
+
336
+ return true
337
+ }
@@ -0,0 +1,29 @@
1
+ # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
+
4
+ name: Learnpack audit
5
+
6
+ on:
7
+ push:
8
+ branches: [ main ]
9
+ pull_request:
10
+ branches: [ main ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [20.x]
20
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
+
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Use Node.js ${{ matrix.node-version }}
25
+ uses: actions/setup-node@v2
26
+ with:
27
+ node-version: ${{ matrix.node-version }}
28
+ - run: npm install @learnpack/learnpack@latest -g
29
+ - run: learnpack audit
@@ -0,0 +1,29 @@
1
+ # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2
+ # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3
+
4
+ name: Learnpack audit
5
+
6
+ on:
7
+ push:
8
+ branches: [ main ]
9
+ pull_request:
10
+ branches: [ main ]
11
+
12
+ jobs:
13
+ build:
14
+
15
+ runs-on: ubuntu-latest
16
+
17
+ strategy:
18
+ matrix:
19
+ node-version: [20.x]
20
+ # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21
+
22
+ steps:
23
+ - uses: actions/checkout@v2
24
+ - name: Use Node.js ${{ matrix.node-version }}
25
+ uses: actions/setup-node@v2
26
+ with:
27
+ node-version: ${{ matrix.node-version }}
28
+ - run: npm install @learnpack/learnpack@latest -g
29
+ - run: learnpack audit