@learnpack/learnpack 5.0.274 → 5.0.275

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 (70) hide show
  1. package/README.md +409 -409
  2. package/lib/commands/audit.js +15 -15
  3. package/lib/commands/breakToken.js +19 -19
  4. package/lib/commands/clean.js +3 -3
  5. package/lib/commands/init.js +41 -41
  6. package/lib/commands/logout.js +3 -3
  7. package/lib/commands/serve.js +127 -70
  8. package/lib/creatorDist/assets/index-BfLyIQVh.js +10223 -10342
  9. package/lib/managers/config/index.js +77 -77
  10. package/lib/models/creator.d.ts +7 -0
  11. package/lib/utils/api.d.ts +1 -0
  12. package/lib/utils/api.js +2 -1
  13. package/lib/utils/creatorUtilities.js +14 -14
  14. package/package.json +1 -1
  15. package/src/commands/audit.ts +487 -487
  16. package/src/commands/breakToken.ts +67 -67
  17. package/src/commands/clean.ts +30 -30
  18. package/src/commands/init.ts +650 -650
  19. package/src/commands/logout.ts +38 -38
  20. package/src/commands/publish.ts +522 -522
  21. package/src/commands/serve.ts +162 -107
  22. package/src/commands/start.ts +333 -333
  23. package/src/commands/translate.ts +123 -123
  24. package/src/creator/README.md +54 -54
  25. package/src/creator/eslint.config.js +28 -28
  26. package/src/creator/src/components/syllabus/ContentIndex.tsx +312 -312
  27. package/src/creator/src/i18n.ts +28 -28
  28. package/src/creator/src/index.css +217 -217
  29. package/src/creator/src/locales/en.json +126 -126
  30. package/src/creator/src/locales/es.json +126 -126
  31. package/src/creator/src/utils/configTypes.ts +122 -122
  32. package/src/creator/src/utils/constants.ts +13 -13
  33. package/src/creator/src/utils/creatorUtils.ts +46 -46
  34. package/src/creator/src/utils/eventBus.ts +2 -2
  35. package/src/creator/src/utils/lib.ts +468 -468
  36. package/src/creator/src/utils/socket.ts +61 -61
  37. package/src/creator/src/utils/store.ts +222 -222
  38. package/src/creator/src/vite-env.d.ts +1 -1
  39. package/src/creator/vite.config.ts +13 -13
  40. package/src/creatorDist/assets/index-BfLyIQVh.js +10223 -10342
  41. package/src/managers/config/defaults.ts +49 -49
  42. package/src/managers/config/exercise.ts +364 -364
  43. package/src/managers/config/index.ts +775 -775
  44. package/src/managers/file.ts +236 -236
  45. package/src/managers/server/routes.ts +554 -554
  46. package/src/managers/session.ts +182 -182
  47. package/src/managers/telemetry.ts +188 -188
  48. package/src/models/action.ts +13 -13
  49. package/src/models/config-manager.ts +28 -28
  50. package/src/models/config.ts +106 -106
  51. package/src/models/creator.ts +47 -40
  52. package/src/models/exercise-obj.ts +30 -30
  53. package/src/models/session.ts +39 -39
  54. package/src/models/socket.ts +61 -61
  55. package/src/models/status.ts +16 -16
  56. package/src/ui/_app/app.js +141 -141
  57. package/src/ui/app.tar.gz +0 -0
  58. package/src/utils/BaseCommand.ts +56 -56
  59. package/src/utils/api.ts +31 -30
  60. package/src/utils/audit.ts +392 -392
  61. package/src/utils/checkNotInstalled.ts +267 -267
  62. package/src/utils/configBuilder.ts +82 -82
  63. package/src/utils/convertCreds.js +34 -34
  64. package/src/utils/creatorUtilities.ts +504 -504
  65. package/src/utils/incrementVersion.js +74 -74
  66. package/src/utils/misc.ts +58 -58
  67. package/src/utils/rigoActions.ts +500 -500
  68. package/src/utils/sidebarGenerator.ts +195 -195
  69. package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  70. package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
@@ -44,11 +44,8 @@ import {
44
44
  // import { handleAssetCreation } from "./publish"
45
45
  import axios from "axios"
46
46
  import * as FormData from "form-data"
47
- import api, { RIGOBOT_HOST } from "../utils/api"
48
- import {
49
- createUploadMiddleware,
50
- minutesToISO8601Duration,
51
- } from "../utils/misc"
47
+ import api, { RIGOBOT_HOST, RIGOBOT_REALTIME_HOST } from "../utils/api"
48
+ import { createUploadMiddleware, minutesToISO8601Duration } from "../utils/misc"
52
49
  import { buildConfig, ConfigResponse } from "../utils/configBuilder"
53
50
  import { checkReadability, slugify } from "../utils/creatorUtilities"
54
51
  import { checkAndFixSidebarPure } from "../utils/sidebarGenerator"
@@ -128,7 +125,6 @@ export const processImage = async (
128
125
  callbackUrl: webhookUrl,
129
126
  })
130
127
 
131
- // console.log("✅ Image", imagePath, "generated successfully!")
132
128
  return true
133
129
  } catch {
134
130
  return false
@@ -237,7 +233,7 @@ async function startExerciseGeneration(
237
233
  courseSlug: string,
238
234
  purposeSlug: string,
239
235
  lastLesson = ""
240
- ): Promise<void> {
236
+ ): Promise<number> {
241
237
  const exSlug = slugify(exercise.id + "-" + exercise.title)
242
238
  console.log("Starting generation of", exSlug)
243
239
 
@@ -263,6 +259,7 @@ async function startExerciseGeneration(
263
259
  )
264
260
 
265
261
  console.log("README CREATOR RES", res)
262
+ return res.id
266
263
  }
267
264
 
268
265
  async function createInitialReadme(
@@ -649,7 +646,7 @@ export default class ServeCommand extends SessionCommand {
649
646
  }
650
647
  }
651
648
 
652
- await startExerciseGeneration(
649
+ const completionId = await startExerciseGeneration(
653
650
  rigoToken,
654
651
  syllabusJson.lessons,
655
652
  syllabusJson.courseInfo,
@@ -662,6 +659,14 @@ export default class ServeCommand extends SessionCommand {
662
659
  )
663
660
 
664
661
  syllabusJson.lessons[parseInt(position)].status = "GENERATING"
662
+ syllabusJson.lessons[parseInt(position)].translations = {
663
+ [syllabusJson.courseInfo.language || "en"]: {
664
+ completionId,
665
+ startedAt: Date.now(),
666
+ completedAt: 0,
667
+ },
668
+ }
669
+ console.log("Lesson", syllabusJson.lessons[parseInt(position)])
665
670
  if (
666
671
  syllabusJson.feedback &&
667
672
  typeof syllabusJson.feedback === "string"
@@ -681,6 +686,27 @@ export default class ServeCommand extends SessionCommand {
681
686
  }
682
687
  )
683
688
 
689
+ app.post("/actions/generate-image/:courseSlug", async (req, res) => {
690
+ const rigoToken = req.header("x-rigo-token")
691
+ const { courseSlug } = req.params
692
+ const { image } = req.body
693
+
694
+ if (!image) {
695
+ return res.status(400).json({
696
+ error: "Image is required",
697
+ })
698
+ }
699
+
700
+ if (!rigoToken) {
701
+ return res.status(400).json({
702
+ error: "Rigo token is required. x-rigo-token header is missing",
703
+ })
704
+ }
705
+
706
+ await processImage(image.url, image.alt, rigoToken, courseSlug)
707
+ res.json({ status: "QUEUED" })
708
+ })
709
+
684
710
  app.post(
685
711
  "/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken",
686
712
  async (req, res) => {
@@ -772,7 +798,7 @@ export default class ServeCommand extends SessionCommand {
772
798
  })
773
799
  }
774
800
 
775
- let nextStarted = false
801
+ let nextCompletionId: number | null = null
776
802
  if (
777
803
  nextExercise &&
778
804
  (exerciseIndex === 0 || !(exerciseIndex % 3 === 0))
@@ -782,7 +808,7 @@ export default class ServeCommand extends SessionCommand {
782
808
  feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabusJson.feedback}`
783
809
  }
784
810
 
785
- startExerciseGeneration(
811
+ nextCompletionId = await startExerciseGeneration(
786
812
  rigoToken,
787
813
  syllabusJson.lessons,
788
814
  syllabusJson.courseInfo,
@@ -791,7 +817,6 @@ export default class ServeCommand extends SessionCommand {
791
817
  syllabusJson.courseInfo.purpose,
792
818
  readme.parsed.content + "\n\n" + feedback
793
819
  )
794
- nextStarted = true
795
820
  } else {
796
821
  console.log(
797
822
  "Stopping generation process at",
@@ -805,32 +830,60 @@ export default class ServeCommand extends SessionCommand {
805
830
  readability.newMarkdown
806
831
  )
807
832
 
808
- if (imagesArray.length > 0) {
809
- emitToCourse(courseSlug, "course-creation", {
810
- lesson: exSlug,
811
- status: "pending",
812
- log: `🔄 Generating images for ${exercise.title}`,
813
- })
814
- for (const image of imagesArray) {
815
- // eslint-disable-next-line no-await-in-loop
816
- await processImage(image.url, image.alt, rigoToken, courseSlug)
817
- }
818
- }
819
-
820
833
  const newSyllabus = {
821
834
  ...syllabusJson,
822
835
  lessons: syllabusJson.lessons.map((lesson, index) => {
823
836
  if (index === exerciseIndex) {
824
- return { ...lesson, generated: true, status: "DONE" }
837
+ const currentTranslations = lesson.translations || {}
838
+ let currentTranslation =
839
+ currentTranslations[syllabusJson.courseInfo.language || "en"]
840
+ if (currentTranslation) {
841
+ currentTranslation.completedAt = Date.now()
842
+ } else {
843
+ currentTranslation = {
844
+ completionId: readme.id,
845
+ startedAt: Date.now(),
846
+ completedAt: Date.now(),
847
+ }
848
+ }
849
+
850
+ currentTranslations[syllabusJson.courseInfo.language || "en"] =
851
+ currentTranslation
852
+ return {
853
+ ...lesson,
854
+ generated: true,
855
+ status: "DONE",
856
+ translations: {
857
+ [syllabusJson.courseInfo.language || "en"]: {
858
+ completionId: nextCompletionId,
859
+ completedAt: Date.now(),
860
+ },
861
+ },
862
+ }
825
863
  }
826
864
 
827
- if (nextExercise && nextExercise.id === lesson.id && nextStarted) {
828
- return { ...lesson, generated: false, status: "GENERATING" }
865
+ if (
866
+ nextExercise &&
867
+ nextExercise.id === lesson.id &&
868
+ nextCompletionId
869
+ ) {
870
+ return {
871
+ ...lesson,
872
+ generated: false,
873
+ status: "GENERATING",
874
+ translations: {
875
+ [syllabusJson.courseInfo.language || "en"]: {
876
+ completionId: nextCompletionId,
877
+ startedAt: Date.now(),
878
+ },
879
+ },
880
+ }
829
881
  }
830
882
 
831
883
  return { ...lesson }
832
884
  }),
833
885
  }
886
+ console.log("New syllabus", newSyllabus)
834
887
  await uploadFileToBucket(
835
888
  bucket,
836
889
  JSON.stringify(newSyllabus),
@@ -1441,7 +1494,7 @@ export default class ServeCommand extends SessionCommand {
1441
1494
 
1442
1495
  const lastResult = "---"
1443
1496
 
1444
- await startExerciseGeneration(
1497
+ const completionId = await startExerciseGeneration(
1445
1498
  rigoToken,
1446
1499
  syllabus.lessons,
1447
1500
  syllabus.courseInfo,
@@ -1450,6 +1503,15 @@ export default class ServeCommand extends SessionCommand {
1450
1503
  syllabus.courseInfo.purpose,
1451
1504
  lastResult
1452
1505
  )
1506
+ if (firstLesson) {
1507
+ firstLesson.translations = {
1508
+ [syllabus.courseInfo.language || "en"]: {
1509
+ completionId,
1510
+ startedAt: Date.now(),
1511
+ completedAt: 0,
1512
+ },
1513
+ }
1514
+ }
1453
1515
 
1454
1516
  await createInitialReadme(
1455
1517
  JSON.stringify(syllabus.courseInfo),
@@ -1479,52 +1541,52 @@ export default class ServeCommand extends SessionCommand {
1479
1541
  }
1480
1542
  })
1481
1543
 
1482
- app.post("/actions/continue-course/:courseSlug", async (req, res) => {
1483
- console.log("POST /actions/continue-course/:courseSlug")
1484
- const { courseSlug } = req.params
1485
-
1486
- const { feedback }: { feedback: string } = req.body
1487
-
1488
- const rigoToken = req.header("x-rigo-token")
1489
- const bcToken = req.header("x-breathecode-token")
1490
- if (!rigoToken || !bcToken) {
1491
- return res.status(400).json({ error: "Missing tokens" })
1492
- }
1493
-
1494
- const syllabus = await bucket.file(
1495
- `courses/${courseSlug}/.learn/initialSyllabus.json`
1496
- )
1497
- const [content] = await syllabus.download()
1498
- const syllabusJson: Syllabus = JSON.parse(content.toString())
1499
- const notGeneratedLessons = syllabusJson.lessons.filter(
1500
- lesson => !lesson.generated
1501
- )
1502
-
1503
- const lastGeneratedLesson = findLast(
1504
- syllabusJson.lessons,
1505
- lesson => lesson.generated ?? false
1506
- )
1507
-
1508
- console.log("ABout to generate", notGeneratedLessons.length, "lessons")
1509
-
1510
- const firstLessonToGenerate = notGeneratedLessons[0]
1511
-
1512
- await startExerciseGeneration(
1513
- rigoToken,
1514
- syllabusJson.lessons,
1515
- syllabusJson.courseInfo,
1516
- firstLessonToGenerate,
1517
- courseSlug,
1518
- syllabusJson.courseInfo.purpose,
1519
- JSON.stringify(lastGeneratedLesson) +
1520
- `\n\nThe user provided this feedback in relation to the course: ${feedback}`
1521
- )
1522
-
1523
- return res.json({
1524
- message: "Course continued",
1525
- slug: courseSlug,
1526
- })
1527
- })
1544
+ // app.post("/actions/continue-course/:courseSlug", async (req, res) => {
1545
+ // console.log("POST /actions/continue-course/:courseSlug")
1546
+ // const { courseSlug } = req.params
1547
+
1548
+ // const { feedback }: { feedback: string } = req.body
1549
+
1550
+ // const rigoToken = req.header("x-rigo-token")
1551
+ // const bcToken = req.header("x-breathecode-token")
1552
+ // if (!rigoToken || !bcToken) {
1553
+ // return res.status(400).json({ error: "Missing tokens" })
1554
+ // }
1555
+
1556
+ // const syllabus = await bucket.file(
1557
+ // `courses/${courseSlug}/.learn/initialSyllabus.json`
1558
+ // )
1559
+ // const [content] = await syllabus.download()
1560
+ // const syllabusJson: Syllabus = JSON.parse(content.toString())
1561
+ // const notGeneratedLessons = syllabusJson.lessons.filter(
1562
+ // lesson => !lesson.generated
1563
+ // )
1564
+
1565
+ // const lastGeneratedLesson = findLast(
1566
+ // syllabusJson.lessons,
1567
+ // lesson => lesson.generated ?? false
1568
+ // )
1569
+
1570
+ // console.log("ABout to generate", notGeneratedLessons.length, "lessons")
1571
+
1572
+ // const firstLessonToGenerate = notGeneratedLessons[0]
1573
+
1574
+ // const completionId = await startExerciseGeneration(
1575
+ // rigoToken,
1576
+ // syllabusJson.lessons,
1577
+ // syllabusJson.courseInfo,
1578
+ // firstLessonToGenerate,
1579
+ // courseSlug,
1580
+ // syllabusJson.courseInfo.purpose,
1581
+ // JSON.stringify(lastGeneratedLesson) +
1582
+ // `\n\nThe user provided this feedback in relation to the course: ${feedback}`
1583
+ // )
1584
+
1585
+ // return res.json({
1586
+ // message: "Course continued",
1587
+ // slug: courseSlug,
1588
+ // })
1589
+ // })
1528
1590
 
1529
1591
  app.get(
1530
1592
  "/courses/:courseSlug/exercises/:exerciseSlug/",
@@ -1746,11 +1808,9 @@ export default class ServeCommand extends SessionCommand {
1746
1808
  return res.send(content.toString("utf-8"))
1747
1809
  } catch (error) {
1748
1810
  console.error("❌ Error fetching file:", error)
1749
- return res
1750
- .status(500)
1751
- .json({
1752
- error: (error as Error).message || "Unable to fetch file",
1753
- })
1811
+ return res.status(500).json({
1812
+ error: (error as Error).message || "Unable to fetch file",
1813
+ })
1754
1814
  }
1755
1815
  }
1756
1816
  )
@@ -1766,38 +1826,35 @@ export default class ServeCommand extends SessionCommand {
1766
1826
 
1767
1827
  if (ytMatch) {
1768
1828
  const videoId = ytMatch[1]
1769
- console.log("VIDEO ID", videoId)
1770
- // fetch metadata
1771
- const items = await YoutubeTranscript.fetchTranscript(videoId)
1772
- console.log("ITEMS FROM YOUTUBE TRANSCRIPT", items)
1773
- const transcript = items.map(i => i.text).join(" ")
1774
-
1775
- let meta: any = null
1776
- try {
1777
- const { data: meta } = await axios.get(
1778
- "https://www.youtube.com/oembed",
1779
- {
1780
- params: { url: decoded, format: "json" },
1781
- }
1782
- )
1783
- console.log("META", meta)
1784
- } catch (error) {
1785
- console.error("ERROR FETCHING META", error)
1786
- meta = null
1787
- }
1788
-
1789
- throw new Error("test")
1829
+ const resFromRigo = await axios.get(
1830
+ `${RIGOBOT_REALTIME_HOST}/actions/youtube-transcript/${videoId}`
1831
+ )
1832
+ console.log("RES FROM RIGO", resFromRigo.data)
1833
+ const transcript = resFromRigo.data.transcript
1834
+
1835
+ // let meta: any = null
1836
+ // try {
1837
+ // const { data: meta } = await axios.get(
1838
+ // "https://www.youtube.com/oembed",
1839
+ // {
1840
+ // params: { url: decoded, format: "json" },
1841
+ // }
1842
+ // )
1843
+ // console.log("META", meta)
1844
+ // } catch (error) {
1845
+ // console.error("ERROR FETCHING META", error)
1846
+ // meta = null
1847
+ // }
1790
1848
 
1791
1849
  return res.json({
1792
1850
  url: decoded,
1793
- title: meta?.title || null,
1794
- author: meta?.author_name || null,
1795
- thumbnail: meta?.thumbnail_url || null,
1851
+ title: resFromRigo.data.title || null,
1852
+ author: resFromRigo.data.author || null,
1796
1853
  transcript,
1797
1854
  })
1798
1855
  }
1799
-
1800
- console.log("NOT A YOUTUBE LINK", decoded)
1856
+
1857
+ console.log("NOT A YOUTUBE LINK", decoded)
1801
1858
 
1802
1859
  const response = await axios.get(decoded, { responseType: "text" })
1803
1860
  const html = response.data as string
@@ -1812,9 +1869,7 @@ export default class ServeCommand extends SessionCommand {
1812
1869
  })
1813
1870
  } catch (error: any) {
1814
1871
  console.error("❌ /actions/fetch error:", error.message || error)
1815
- res
1816
- .status(500)
1817
- .json({ error: error.message || "Failed to fetch link" })
1872
+ res.status(500).json({ error: error.message || "Failed to fetch link" })
1818
1873
  }
1819
1874
  })
1820
1875
  app.delete("/packages/:slug", async (req, res) => {