@learnpack/learnpack 5.0.361 → 5.0.362

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.
@@ -10,7 +10,7 @@
10
10
  />
11
11
 
12
12
  <title>Learnpack Creator: Craft tutorials in seconds!</title>
13
- <script type="module" crossorigin src="/creator/assets/index-CqVEKiJR.js"></script>
13
+ <script type="module" crossorigin src="/creator/assets/index-CKg5uuoj.js"></script>
14
14
  <link rel="stylesheet" crossorigin href="/creator/assets/index-B7s5Nmq8.css">
15
15
  </head>
16
16
  <body>
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Whether a lesson README holds generated content. The file starts as a
3
+ * `loader` block, and the generated content replaces it.
4
+ * @param readme - The README text, or null when the file does not exist.
5
+ * @returns True when the lesson has its generated content.
6
+ */
7
+ export declare const hasGeneratedContent: (readme: string | null) => boolean;
8
+ /**
9
+ * Finds the lessons marked as ERROR that were generated anyway. Publishing and
10
+ * translating wait for every lesson, and the retry screen only shows while the
11
+ * README has its `loader`, so these lessons would otherwise stay stuck.
12
+ * @param lessons - The lessons of the syllabus.
13
+ * @param readReadme - Reads the README of a lesson, or null if it has none.
14
+ * @returns The lessons to mark as done.
15
+ */
16
+ export declare const findFinishedLessons: <T extends {
17
+ status?: string;
18
+ }>(lessons: T[], readReadme: (lesson: T) => Promise<string | null>) => Promise<T[]>;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findFinishedLessons = exports.hasGeneratedContent = void 0;
4
+ /**
5
+ * Whether a lesson README holds generated content. The file starts as a
6
+ * `loader` block, and the generated content replaces it.
7
+ * @param readme - The README text, or null when the file does not exist.
8
+ * @returns True when the lesson has its generated content.
9
+ */
10
+ const hasGeneratedContent = (readme) => readme !== null && readme.trim() !== "" && !readme.includes("```loader");
11
+ exports.hasGeneratedContent = hasGeneratedContent;
12
+ /**
13
+ * Finds the lessons marked as ERROR that were generated anyway. Publishing and
14
+ * translating wait for every lesson, and the retry screen only shows while the
15
+ * README has its `loader`, so these lessons would otherwise stay stuck.
16
+ * @param lessons - The lessons of the syllabus.
17
+ * @param readReadme - Reads the README of a lesson, or null if it has none.
18
+ * @returns The lessons to mark as done.
19
+ */
20
+ const findFinishedLessons = async (lessons, readReadme) => {
21
+ const finished = [];
22
+ for (const lesson of lessons) {
23
+ if (lesson.status !== "ERROR")
24
+ continue;
25
+ // eslint-disable-next-line no-await-in-loop
26
+ if ((0, exports.hasGeneratedContent)(await readReadme(lesson)))
27
+ finished.push(lesson);
28
+ }
29
+ return finished;
30
+ };
31
+ exports.findFinishedLessons = findFinishedLessons;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@learnpack/learnpack",
3
3
  "description": "Seamlessly build, sell and/or take interactive & auto-graded tutorials, start learning now or build a new tutorial to your audience.",
4
- "version": "5.0.361",
4
+ "version": "5.0.362",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -48,6 +48,7 @@ import {
48
48
  getReadmeExtension,
49
49
  insertStepInCorrectPosition,
50
50
  } from "../utils/creatorUtilities"
51
+ import { findFinishedLessons } from "../utils/lessonRecovery"
51
52
  // import { handleAssetCreation } from "./publish"
52
53
  import axios from "axios"
53
54
  import * as FormData from "form-data"
@@ -680,6 +681,10 @@ async function startInitialContentGeneration(
680
681
  )
681
682
 
682
683
  console.log("INITIAL CONTENT GENERATOR RES", res)
684
+ if (!res) {
685
+ throw new Error(`Could not start generating lesson ${exSlug}`)
686
+ }
687
+
683
688
  return res.id
684
689
  }
685
690
 
@@ -1020,6 +1025,45 @@ async function updateLessonStatusToDone(
1020
1025
  }
1021
1026
  }
1022
1027
 
1028
+ const lessonSlug = (lesson: Lesson) => slugify(lesson.id + "-" + lesson.title)
1029
+
1030
+ async function repairFinishedLessons(
1031
+ courseSlug: string,
1032
+ syllabus: Syllabus,
1033
+ bucket: Bucket
1034
+ ): Promise<void> {
1035
+ const language = syllabus.courseInfo.language || "en"
1036
+
1037
+ try {
1038
+ const finished = await findFinishedLessons(
1039
+ syllabus.lessons ?? [],
1040
+ async lesson => {
1041
+ const readme = bucket.file(
1042
+ `courses/${courseSlug}/exercises/${lessonSlug(
1043
+ lesson
1044
+ )}/README${getReadmeExtension(language)}`
1045
+ )
1046
+ const [exists] = await readme.exists()
1047
+ if (!exists) return null
1048
+
1049
+ const [content] = await readme.download()
1050
+ return content.toString()
1051
+ }
1052
+ )
1053
+
1054
+ for (const lesson of finished) {
1055
+ // eslint-disable-next-line no-await-in-loop
1056
+ await updateLessonStatusToDone(courseSlug, lessonSlug(lesson), bucket)
1057
+ lesson.status = "DONE"
1058
+ lesson.generated = true
1059
+ }
1060
+ } catch (error) {
1061
+ // Loading the course matters more than repairing it now. The next load
1062
+ // tries again.
1063
+ console.error(`Error repairing lessons of ${courseSlug}:`, error)
1064
+ }
1065
+ }
1066
+
1023
1067
  async function continueWithNextLesson(
1024
1068
  courseSlug: string,
1025
1069
  currentExerciseIndex: number,
@@ -1043,15 +1087,26 @@ async function continueWithNextLesson(
1043
1087
  feedback = `\n\nThe user added the following feedback with relation to the previous generations: ${syllabus.feedback}`
1044
1088
  }
1045
1089
 
1046
- nextCompletionId = await startInitialContentGeneration(
1047
- rigoToken,
1048
- syllabus.lessons,
1049
- syllabus.courseInfo,
1050
- nextExercise,
1051
- courseSlug,
1052
- syllabus.courseInfo.purpose,
1053
- finalContent + "\n\n" + feedback
1054
- )
1090
+ try {
1091
+ nextCompletionId = await startInitialContentGeneration(
1092
+ rigoToken,
1093
+ syllabus.lessons,
1094
+ syllabus.courseInfo,
1095
+ nextExercise,
1096
+ courseSlug,
1097
+ syllabus.courseInfo.purpose,
1098
+ finalContent + "\n\n" + feedback
1099
+ )
1100
+ } catch (error) {
1101
+ // The current lesson is finished either way. The next one stays PENDING,
1102
+ // and its screen offers to continue.
1103
+ console.error("Error starting the next lesson:", error)
1104
+ emitToCourse(courseSlug, "course-creation", {
1105
+ lesson: slugify(nextExercise.id + "-" + nextExercise.title),
1106
+ status: "error",
1107
+ log: `❌ Could not start lesson ${nextExercise.title}`,
1108
+ })
1109
+ }
1055
1110
  } else {
1056
1111
  console.log(
1057
1112
  "Stopping generation process at",
@@ -5239,6 +5294,7 @@ class ServeCommand extends SessionCommand {
5239
5294
  const [content] = await syllabus.download()
5240
5295
  const syllabusJson: Syllabus = JSON.parse(content.toString())
5241
5296
  sanitizeSyllabusFromUnknown(syllabusJson)
5297
+ await repairFinishedLessons(courseSlug, syllabusJson, bucket)
5242
5298
  res.json(syllabusJson)
5243
5299
  } catch (error) {
5244
5300
  console.error("Error getting syllabus:", error)