@learnpack/learnpack 5.0.204 → 5.0.209

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.
package/README.md CHANGED
@@ -21,7 +21,7 @@ $ npm install -g @learnpack/learnpack
21
21
  $ learnpack COMMAND
22
22
  running command...
23
23
  $ learnpack (-v|--version|version)
24
- @learnpack/learnpack/5.0.204 win32-x64 node-v22.15.0
24
+ @learnpack/learnpack/5.0.209 win32-x64 node-v22.15.0
25
25
  $ learnpack --help [COMMAND]
26
26
  USAGE
27
27
  $ learnpack COMMAND
@@ -80,7 +80,7 @@ DESCRIPTION
80
80
  12. If there is a file within the exercises folder but not inside of any particular exercise's folder. (Warning)
81
81
  ```
82
82
 
83
- _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\audit.ts)_
83
+ _See code: [src\commands\audit.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\audit.ts)_
84
84
 
85
85
  ## `learnpack breakToken`
86
86
 
@@ -95,7 +95,7 @@ OPTIONS
95
95
  -y, --yes Skip all prompts and initialize an empty project
96
96
  ```
97
97
 
98
- _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\breakToken.ts)_
98
+ _See code: [src\commands\breakToken.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\breakToken.ts)_
99
99
 
100
100
  ## `learnpack clean`
101
101
 
@@ -110,7 +110,7 @@ DESCRIPTION
110
110
  Extra documentation goes here
111
111
  ```
112
112
 
113
- _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\clean.ts)_
113
+ _See code: [src\commands\clean.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\clean.ts)_
114
114
 
115
115
  ## `learnpack download [PACKAGE]`
116
116
 
@@ -128,7 +128,7 @@ DESCRIPTION
128
128
  Extra documentation goes here
129
129
  ```
130
130
 
131
- _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\download.ts)_
131
+ _See code: [src\commands\download.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\download.ts)_
132
132
 
133
133
  ## `learnpack help [COMMAND]`
134
134
 
@@ -160,7 +160,7 @@ OPTIONS
160
160
  -y, --yes Skip all prompts and initialize an empty project
161
161
  ```
162
162
 
163
- _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\init.ts)_
163
+ _See code: [src\commands\init.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\init.ts)_
164
164
 
165
165
  ## `learnpack login [PACKAGE]`
166
166
 
@@ -178,7 +178,7 @@ DESCRIPTION
178
178
  Extra documentation goes here
179
179
  ```
180
180
 
181
- _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\login.ts)_
181
+ _See code: [src\commands\login.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\login.ts)_
182
182
 
183
183
  ## `learnpack logout [PACKAGE]`
184
184
 
@@ -196,7 +196,7 @@ DESCRIPTION
196
196
  Extra documentation goes here
197
197
  ```
198
198
 
199
- _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\logout.ts)_
199
+ _See code: [src\commands\logout.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\logout.ts)_
200
200
 
201
201
  ## `learnpack plugins`
202
202
 
@@ -328,7 +328,7 @@ OPTIONS
328
328
  -s, --strict strict mode
329
329
  ```
330
330
 
331
- _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\publish.ts)_
331
+ _See code: [src\commands\publish.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\publish.ts)_
332
332
 
333
333
  ## `learnpack serve`
334
334
 
@@ -345,7 +345,7 @@ OPTIONS
345
345
  -y, --yes Skip all prompts and initialize an empty project
346
346
  ```
347
347
 
348
- _See code: [src\commands\serve.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\serve.ts)_
348
+ _See code: [src\commands\serve.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\serve.ts)_
349
349
 
350
350
  ## `learnpack start`
351
351
 
@@ -367,7 +367,7 @@ OPTIONS
367
367
  -y, --yes Skip all prompts and initialize an empty project
368
368
  ```
369
369
 
370
- _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\start.ts)_
370
+ _See code: [src\commands\start.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\start.ts)_
371
371
 
372
372
  ## `learnpack test [EXERCISESLUG]`
373
373
 
@@ -384,7 +384,7 @@ OPTIONS
384
384
  -y, --yes Skip all prompts and initialize an empty project
385
385
  ```
386
386
 
387
- _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\test.ts)_
387
+ _See code: [src\commands\test.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\test.ts)_
388
388
 
389
389
  ## `learnpack translate`
390
390
 
@@ -398,7 +398,7 @@ OPTIONS
398
398
  -y, --yes Skip all prompts and initialize an empty project
399
399
  ```
400
400
 
401
- _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.204/src\commands\translate.ts)_
401
+ _See code: [src\commands\translate.ts](https://github.com/learnpack/learnpack-cli/blob/v5.0.209/src\commands\translate.ts)_
402
402
  <!-- commandsstop -->
403
403
 
404
404
  > > > > > > > 0cb3e56d84c197f9d008836bb573eade212b7e57
@@ -63,6 +63,7 @@ async function processExercise(rigoToken, steps, packageContext, exercise, exerc
63
63
  tutorial_description: packageContext,
64
64
  lesson_description: description,
65
65
  kind: kind.toLowerCase(),
66
+ last_lesson: "",
66
67
  }, "learnpack-lesson-writer");
67
68
  const duration = durationByKind[kind.toLowerCase()];
68
69
  let attempts = 0;
@@ -21,7 +21,7 @@ export declare const createLearnJson: (courseInfo: FormState) => {
21
21
  preview: string;
22
22
  };
23
23
  export declare const processImage: (bucket: Bucket, tutorialDir: string, url: string, description: string, rigoToken: string) => Promise<boolean>;
24
- export declare function processExercise(bucket: Bucket, rigoToken: string, steps: Lesson[], packageContext: FormState, exercise: Lesson, exercisesDir: string, courseSlug: string, purposeSlug: string): Promise<string>;
24
+ export declare function processExercise(bucket: Bucket, rigoToken: string, steps: Lesson[], packageContext: FormState, exercise: Lesson, tutorialDir: string, courseSlug: string, purposeSlug: string, lastLesson?: string): Promise<string>;
25
25
  export default class ServeCommand extends SessionCommand {
26
26
  static description: string;
27
27
  static flags: any;
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processImage = exports.createLearnJson = void 0;
4
4
  exports.processExercise = processExercise;
5
+ const tslib_1 = require("tslib");
5
6
  const command_1 = require("@oclif/command");
6
7
  // import { readDocument } from "../utils/readDocuments"
7
8
  const youtube_transcript_1 = require("youtube-transcript");
@@ -33,25 +34,15 @@ const sidebarGenerator_1 = require("../utils/sidebarGenerator");
33
34
  const publish_1 = require("./publish");
34
35
  const frontMatter = require("front-matter");
35
36
  dotenv.config();
36
- const validateLanguage = (language) => {
37
- if (!language || language.length !== 2) {
38
- return "en";
37
+ function findLast(array, predicate) {
38
+ for (let i = array.length - 1; i >= 0; i--) {
39
+ if (predicate(array[i]))
40
+ return array[i];
39
41
  }
40
- return language;
41
- };
42
- function fixSlugLength(slug) {
43
- let clean = slug.toLowerCase();
44
- clean = clean.replace(/[^\da-z-]+/g, "-");
45
- clean = clean.replace(/-+/g, "-");
46
- clean = clean.slice(0, 49);
47
- clean = clean.replace(/^-+/, "").replace(/-+$/, "");
48
- clean = clean.replace(/^[^\da-z]+/, "").replace(/[^\da-z]+$/, "");
49
- if (!clean)
50
- throw new Error("Invalid slug after cleaning");
51
- return clean;
42
+ return undefined;
52
43
  }
53
44
  const createLearnJson = (courseInfo) => {
54
- console.log("courseInfo to create learn json", courseInfo);
45
+ // console.log("courseInfo to create learn json", courseInfo)
55
46
  const expectedPreviewUrl = `https://${(0, creatorUtilities_2.slugify)(courseInfo.title)}.learn-pack.com/preview.png`;
56
47
  console.log("Preview url in generated learn.json", expectedPreviewUrl);
57
48
  const language = courseInfo.language || "en";
@@ -118,16 +109,53 @@ const processImage = async (bucket, tutorialDir, url, description, rigoToken) =>
118
109
  }
119
110
  };
120
111
  exports.processImage = processImage;
121
- const createInitialSidebar = async (slugs) => {
112
+ const createInitialSidebar = async (slugs, initialLanguage = "en") => {
113
+ const language = initialLanguage === "en" ? "us" : initialLanguage;
122
114
  const sidebar = {};
123
115
  for (const slug of slugs) {
124
116
  sidebar[slug] = {
125
- us: slug,
117
+ [language]: slug,
126
118
  };
127
119
  }
128
120
  return sidebar;
129
121
  };
130
- async function processExercise(bucket, rigoToken, steps, packageContext, exercise, exercisesDir, courseSlug, purposeSlug) {
122
+ const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) => {
123
+ const isGeneratingText = `
124
+ \`\`\`loader slug="${exSlug}"
125
+ :rigo
126
+ \`\`\`
127
+ `;
128
+ const readmeFilename = `README.${packageContext.language && packageContext.language !== "en" ?
129
+ `${packageContext.language}.` :
130
+ ""}md`;
131
+ await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
132
+ };
133
+ const cleanFormState = (formState) => {
134
+ // keysToDelete: description, technologies, purpose
135
+ const { description, technologies, purpose, hasContentIndex, duration, isCompleted, variables, currentStep, language } = formState, rest = tslib_1.__rest(formState, ["description", "technologies", "purpose", "hasContentIndex", "duration", "isCompleted", "variables", "currentStep", "language"]);
136
+ return rest;
137
+ };
138
+ async function startExerciseGeneration(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
139
+ const exercisesDir = `${tutorialDir}/exercises`;
140
+ const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
141
+ console.log("exSlug", exSlug);
142
+ const readmeFilename = `README.${packageContext.language && packageContext.language !== "en" ?
143
+ `${packageContext.language}.` :
144
+ ""}md`;
145
+ const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/exercise-processor/${exercise.id}/${rigoToken}`;
146
+ const res = await (0, rigoActions_1.readmeCreator)(rigoToken, {
147
+ title: `${exercise.id} - ${exercise.title}`,
148
+ output_lang: packageContext.language || "en",
149
+ list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
150
+ tutorial_description: JSON.stringify(cleanFormState(packageContext)),
151
+ lesson_description: exercise.description,
152
+ kind: exercise.type.toLowerCase(),
153
+ last_lesson: lastLesson,
154
+ }, purposeSlug, webhookUrl);
155
+ // console.log("res processing in background", res)
156
+ }
157
+ async function processExercise(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
158
+ const exercisesDir = `${tutorialDir}/exercises`;
131
159
  // const tid = toast.loading("Generating lesson...")
132
160
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
133
161
  console.log("exSlug", exSlug);
@@ -136,62 +164,70 @@ async function processExercise(bucket, rigoToken, steps, packageContext, exercis
136
164
  ""}md`;
137
165
  const targetDir = `${exercisesDir}/${exSlug}`;
138
166
  console.log("✍🏻 Generating lesson", exercise.id, exercise.title);
139
- const isGeneratingText = `
140
- \`\`\`loader slug="${exSlug}"
141
- :rigo
142
- \`\`\`
143
- `;
144
- await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
145
167
  const readme = await (0, rigoActions_1.readmeCreator)(rigoToken, {
146
168
  title: `${exercise.id} - ${exercise.title}`,
147
169
  output_lang: packageContext.language || "en",
148
- list_of_exercises: JSON.stringify(steps),
149
- tutorial_description: JSON.stringify(packageContext),
170
+ list_of_exercises: JSON.stringify(steps.map(step => step.id + "-" + step.title)),
171
+ tutorial_description: JSON.stringify(cleanFormState(packageContext)),
150
172
  lesson_description: exercise.description,
151
173
  kind: exercise.type.toLowerCase(),
174
+ last_lesson: lastLesson,
152
175
  }, purposeSlug);
153
176
  const duration = exercise.duration;
154
- let attempts = 0;
155
- let readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, 250, duration || 3);
156
- while (readability.fkglResult.fkgl > PARAMS.max_fkgl &&
157
- attempts < PARAMS.max_rewrite_attempts) {
158
- (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
159
- lesson: exSlug,
160
- status: "generating",
161
- log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
162
- });
163
- // eslint-disable-next-line
164
- const reducedReadme = await (0, rigoActions_1.makeReadmeReadable)(rigoToken, {
165
- lesson: readability.body,
166
- number_of_words: readability.minutes.toString(),
167
- expected_number_words: PARAMS.max_words.toString(),
168
- fkgl_results: JSON.stringify(readability.fkglResult),
169
- expected_grade_level: PARAMS.expected_grade_level,
170
- }, purposeSlug);
171
- if (!reducedReadme)
172
- break;
173
- readability = (0, creatorUtilities_2.checkReadability)(reducedReadme.parsed.content, PARAMS.max_words, duration || 2);
174
- attempts++;
175
- }
176
- await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
177
+ // let attempts = 0
178
+ const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, duration || 3);
177
179
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
178
180
  lesson: exSlug,
179
- status: "done",
180
- log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
181
+ status: "generating",
182
+ log: `🔄 The lesson ${exercise.id} - ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
181
183
  });
182
- if (exercise.type.toLowerCase() === "code") {
184
+ // while (
185
+ // readability.fkglResult.fkgl > PARAMS.max_fkgl &&
186
+ // attempts < PARAMS.max_rewrite_attempts
187
+ // ) {
188
+ // // eslint-disable-next-line
189
+ // const reducedReadme = await makeReadmeReadable(
190
+ // rigoToken,
191
+ // {
192
+ // lesson: readability.body,
193
+ // number_of_words: readability.minutes.toString(),
194
+ // expected_number_words: PARAMS.max_words.toString(),
195
+ // fkgl_results: JSON.stringify(readability.fkglResult),
196
+ // expected_grade_level: PARAMS.expected_grade_level,
197
+ // },
198
+ // purposeSlug
199
+ // )
200
+ // if (!reducedReadme) break
201
+ // readability = checkReadability(
202
+ // reducedReadme.parsed.content,
203
+ // PARAMS.max_words,
204
+ // duration || 3
205
+ // )
206
+ // attempts++
207
+ // }
208
+ await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
209
+ if (exercise.type.toLowerCase() === "code" &&
210
+ readme.parsed.codefile_content) {
183
211
  console.log("🔍 Creating code file for", exercise.title);
184
- const codeFile = await (0, rigoActions_1.createCodeFile)(rigoToken, {
185
- readme: readability.newMarkdown,
186
- tutorial_info: JSON.stringify(packageContext),
187
- });
188
- await uploadFileToBucket(bucket, codeFile.parsed.content, `${targetDir}/index.${codeFile.parsed.extension.replace(".", "")}`);
212
+ await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
213
+ }
214
+ const imagesArray = (0, creatorUtilities_1.extractImagesFromMarkdown)(readability.newMarkdown);
215
+ if (imagesArray.length > 0) {
189
216
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
190
217
  lesson: exSlug,
191
218
  status: "done",
192
- log: `✅ The code file for ${exercise.title} has been generated successfully!`,
219
+ log: `🔄 Generating images for ${exercise.title}`,
193
220
  });
221
+ for (const image of imagesArray) {
222
+ // eslint-disable-next-line no-await-in-loop
223
+ await (0, exports.processImage)(bucket, tutorialDir, image.url, image.alt, rigoToken);
224
+ }
194
225
  }
226
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
227
+ lesson: exSlug,
228
+ status: "done",
229
+ log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
230
+ });
195
231
  return readability.newMarkdown;
196
232
  }
197
233
  const fixPreviewUrl = (slug, previewUrl) => {
@@ -226,6 +262,14 @@ class ServeCommand extends SessionCommand_1.default {
226
262
  credentials,
227
263
  });
228
264
  const bucket = bucketStorage.bucket(process.env.GCP_BUCKET_NAME || "learnpack-packages");
265
+ const host = process.env.HOST;
266
+ if (!host) {
267
+ console.log("HOST is not set");
268
+ process.exit(1);
269
+ }
270
+ else {
271
+ console.log("HOST is set to", host);
272
+ }
229
273
  // async function listFilesWithPrefix(prefix: string) {
230
274
  // const [files] = await bucket.getFiles({ prefix })
231
275
  // return files
@@ -380,6 +424,76 @@ class ServeCommand extends SessionCommand_1.default {
380
424
  (0, creatorSocket_1.emitToNotification)(id, body);
381
425
  res.json({ id, status: "SUCCESS" });
382
426
  });
427
+ app.post("/webhooks/:courseSlug/exercise-processor/:lessonID/:rigoToken", async (req, res) => {
428
+ // console.log("Receiving a webhook to exercise processor")
429
+ const { courseSlug, lessonID, rigoToken } = req.params;
430
+ const readme = req.body;
431
+ const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
432
+ const [content] = await syllabus.download();
433
+ const syllabusJson = JSON.parse(content.toString());
434
+ const exerciseIndex = syllabusJson.lessons.findIndex(lesson => lesson.id === lessonID);
435
+ if (exerciseIndex === -1) {
436
+ console.log("Exercise not found receiving webhook, this should not happen", lessonID);
437
+ return res.json({ status: "ERROR", error: "Exercise not found" });
438
+ }
439
+ const exercise = syllabusJson.lessons[exerciseIndex];
440
+ const nextExercise = syllabusJson.lessons[exerciseIndex + 1] || null;
441
+ if (!exercise) {
442
+ console.log("Exercise not found receiving webhook, this should not happen", lessonID);
443
+ return res.json({ status: "SUCCESS" });
444
+ }
445
+ const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
446
+ const readability = (0, creatorUtilities_2.checkReadability)(readme.parsed.content, PARAMS.max_words, 3);
447
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
448
+ lesson: exSlug,
449
+ status: "generating",
450
+ log: `🔄 The lesson ${exercise.title} has a readability score of ${readability.fkglResult.fkgl}`,
451
+ });
452
+ const exercisesDir = `courses/${courseSlug}/exercises`;
453
+ const targetDir = `${exercisesDir}/${exSlug}`;
454
+ const readmeFilename = `README.${readme.parsed.language_code && readme.parsed.language_code !== "en" ?
455
+ `${readme.parsed.language_code}.` :
456
+ ""}md`;
457
+ await uploadFileToBucket(bucket, readability.newMarkdown, `${targetDir}/${readmeFilename}`);
458
+ if (exercise.type.toLowerCase() === "code" &&
459
+ readme.parsed.codefile_content) {
460
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
461
+ lesson: exSlug,
462
+ status: "generating",
463
+ log: `🔄 Creating code file for ${exercise.title}`,
464
+ });
465
+ await uploadFileToBucket(bucket, readme.parsed.codefile_content, `${targetDir}/${readme.parsed.codefile_name.toLowerCase().trim()}`);
466
+ }
467
+ if (nextExercise) {
468
+ startExerciseGeneration(bucket, rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, nextExercise, `courses/${courseSlug}`, courseSlug, syllabusJson.courseInfo.purpose, readme.parsed.content);
469
+ }
470
+ const imagesArray = (0, creatorUtilities_1.extractImagesFromMarkdown)(readability.newMarkdown);
471
+ if (imagesArray.length > 0) {
472
+ console.log("This course requires images and I don't have the token :)");
473
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
474
+ lesson: exSlug,
475
+ status: "pending",
476
+ log: `🔄 Generating images for ${exercise.title}`,
477
+ });
478
+ for (const image of imagesArray) {
479
+ // eslint-disable-next-line no-await-in-loop
480
+ await (0, exports.processImage)(bucket, `courses/${courseSlug}`, image.url, image.alt, rigoToken);
481
+ }
482
+ }
483
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
484
+ lesson: exSlug,
485
+ status: "done",
486
+ log: `✅ The lesson ${exercise.id} - ${exercise.title} has been generated successfully!`,
487
+ });
488
+ const newSyllabus = Object.assign(Object.assign({}, syllabusJson), { lessons: syllabusJson.lessons.map(lesson => {
489
+ if (lesson.id === exercise.id) {
490
+ return Object.assign(Object.assign({}, lesson), { generated: true });
491
+ }
492
+ return lesson;
493
+ }) });
494
+ await uploadFileToBucket(bucket, JSON.stringify(newSyllabus), `courses/${courseSlug}/.learn/initialSyllabus.json`);
495
+ res.json({ status: "SUCCESS" });
496
+ });
383
497
  app.get("/check-preview-image/:slug", async (req, res) => {
384
498
  const { slug } = req.params;
385
499
  const file = bucket.file(`courses/${slug}/preview.png`);
@@ -662,38 +776,85 @@ class ServeCommand extends SessionCommand_1.default {
662
776
  if (!rigoToken || !bcToken) {
663
777
  return res.status(400).json({ error: "Missing tokens" });
664
778
  }
665
- const slug = (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title);
666
- const tutorialDir = `courses/${slug}`;
779
+ const courseSlug = (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title);
780
+ const tutorialDir = `courses/${courseSlug}`;
667
781
  const learnJson = (0, exports.createLearnJson)(syllabus.courseInfo);
668
782
  try {
669
- await api_1.default.createRigoPackage(rigoToken, slug, learnJson);
783
+ await api_1.default.createRigoPackage(rigoToken, courseSlug, learnJson);
670
784
  }
671
785
  catch (error) {
672
786
  console.error("Failed to create Rigo package:", error);
673
787
  return res.status(400).json({ error: "Failed to create Rigo package" });
674
788
  }
675
- await uploadFileToBucket(bucket, JSON.stringify(learnJson), `${tutorialDir}/learn.json`);
676
- console.log("🔄 Learn.json uploaded to bucket to", tutorialDir);
677
- console.log("🔄 Processing lessons with purpose", syllabus.courseInfo.purpose);
678
- const lessonsPromises = syllabus.lessons.map(lesson => processExercise(bucket, rigoToken, syllabus.lessons, syllabus.courseInfo, lesson, tutorialDir + "/exercises", (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title), syllabus.courseInfo.purpose));
679
- const readmeContents = await Promise.all(lessonsPromises);
680
- let imagesArray = [];
681
- for (const content of readmeContents) {
682
- imagesArray = [...imagesArray, ...(0, creatorUtilities_1.extractImagesFromMarkdown)(content)];
683
- }
684
- console.log("📷 Generating images...");
685
- const imagePromises = imagesArray.map(async (image) => {
686
- return (0, exports.processImage)(bucket, tutorialDir, image.url, image.alt, rigoToken);
687
- });
688
- await Promise.all(imagePromises);
689
- const sidebar = await createInitialSidebar(syllabus.lessons.map(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title)));
789
+ try {
790
+ await uploadFileToBucket(bucket, JSON.stringify(learnJson), `${tutorialDir}/learn.json`);
791
+ }
792
+ catch (error) {
793
+ console.error("Failed to upload learn.json:", error);
794
+ return res.status(400).json({ error: "Failed to upload learn.json" });
795
+ }
796
+ console.log("🔄 Learn.json and initialSyllabus.json uploaded to bucket to", tutorialDir);
797
+ for (let i = 0; i < syllabus.lessons.length; i++) {
798
+ const lesson = syllabus.lessons[i];
799
+ const exSlug = (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title);
800
+ const targetDir = `${tutorialDir}/exercises/${exSlug}`;
801
+ // eslint-disable-next-line no-await-in-loop
802
+ await uploadInitialReadme(bucket, exSlug, targetDir, syllabus.courseInfo);
803
+ }
804
+ const sidebar = await createInitialSidebar(syllabus.lessons.map(lesson => (0, creatorUtilities_2.slugify)(lesson.id + "-" + lesson.title)), syllabus.courseInfo.language);
805
+ const initialSyllabus = Object.assign(Object.assign({}, syllabus), { lessons: syllabus.lessons.map((lesson, index) => {
806
+ if (index < 1) {
807
+ return Object.assign(Object.assign({}, lesson), { generated: true });
808
+ }
809
+ return Object.assign(Object.assign({}, lesson), { generated: false });
810
+ }) });
811
+ await uploadFileToBucket(bucket, JSON.stringify(initialSyllabus), `${tutorialDir}/.learn/initialSyllabus.json`);
690
812
  await uploadFileToBucket(bucket, JSON.stringify(sidebar), `${tutorialDir}/.learn/sidebar.json`);
691
- console.log("SIDEBAR UPLOADED", sidebar);
813
+ const firstLesson = syllabus.lessons[0];
814
+ const lastResult = "Nothing";
815
+ startExerciseGeneration(bucket, rigoToken, syllabus.lessons, syllabus.courseInfo, firstLesson, tutorialDir, courseSlug, syllabus.courseInfo.purpose, lastResult);
692
816
  return res.json({
693
817
  message: "Course created",
694
818
  slug: (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title),
695
819
  });
696
820
  });
821
+ app.get("/courses/:courseSlug/syllabus", async (req, res) => {
822
+ try {
823
+ console.log("GET /courses/:courseSlug/syllabus");
824
+ const { courseSlug } = req.params;
825
+ const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
826
+ const [content] = await syllabus.download();
827
+ const syllabusJson = JSON.parse(content.toString());
828
+ res.json(syllabusJson);
829
+ }
830
+ catch (error) {
831
+ console.error("Error getting syllabus:", error);
832
+ return res.status(500).json({ error: "Error getting syllabus" });
833
+ }
834
+ });
835
+ app.post("/actions/continue-course/:courseSlug", async (req, res) => {
836
+ console.log("POST /actions/continue-course/:courseSlug");
837
+ const { courseSlug } = req.params;
838
+ const { feedback } = req.body;
839
+ const rigoToken = req.header("x-rigo-token");
840
+ const bcToken = req.header("x-breathecode-token");
841
+ if (!rigoToken || !bcToken) {
842
+ return res.status(400).json({ error: "Missing tokens" });
843
+ }
844
+ const syllabus = await bucket.file(`courses/${courseSlug}/.learn/initialSyllabus.json`);
845
+ const [content] = await syllabus.download();
846
+ const syllabusJson = JSON.parse(content.toString());
847
+ const notGeneratedLessons = syllabusJson.lessons.filter(lesson => !lesson.generated);
848
+ const lastGeneratedLesson = findLast(syllabusJson.lessons, lesson => { var _a; return (_a = lesson.generated) !== null && _a !== void 0 ? _a : false; });
849
+ console.log("ABout to generate", notGeneratedLessons.length, "lessons");
850
+ const firstLessonToGenerate = notGeneratedLessons[0];
851
+ await startExerciseGeneration(bucket, rigoToken, syllabusJson.lessons, syllabusJson.courseInfo, firstLessonToGenerate, `courses/${courseSlug}`, courseSlug, syllabusJson.courseInfo.purpose, JSON.stringify(lastGeneratedLesson) +
852
+ `\n\nThe user provided this feedback in relation to the course: ${feedback}`);
853
+ return res.json({
854
+ message: "Course continued",
855
+ slug: courseSlug,
856
+ });
857
+ });
697
858
  app.get("/courses/:courseSlug/exercises/:exerciseSlug/", async (req, res) => {
698
859
  var _a;
699
860
  console.log("GET /courses/:courseSlug/exercises/:exerciseSlug/");
@@ -737,9 +898,7 @@ class ServeCommand extends SessionCommand_1.default {
737
898
  .json({ error: "Authentication failed, missing tokens" });
738
899
  }
739
900
  const { config, exercises } = await (0, configBuilder_1.buildConfig)(bucket, slug);
740
- // console.log(slug, "SLUG")
741
- const fixedSlug = fixSlugLength(slug);
742
- // console.log(fixedSlug, "FIXED SLUG")
901
+ // const fixedSlug = fixSlugLength(slug)
743
902
  const prefix = `courses/${slug}/`;
744
903
  const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`);
745
904
  const buildRoot = path.join(tmpRoot, "build");
@@ -767,7 +926,7 @@ class ServeCommand extends SessionCommand_1.default {
767
926
  .replace(/{{title}}/g, config.title.us)
768
927
  .replace(/<title>.*<\/title>/, `<title>${config.title.us}</title>`)
769
928
  .replace(/{{description}}/g, config.description.us)
770
- .replace(/{{preview}}/g, fixPreviewUrl(fixedSlug, "") ||
929
+ .replace(/{{preview}}/g, fixPreviewUrl(slug, "") ||
771
930
  "https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg")
772
931
  .replace(/{{slug}}/g, slug)
773
932
  .replace(/{{duration}}/g, (0, misc_1.minutesToISO8601Duration)(config.duration));
@@ -788,13 +947,13 @@ class ServeCommand extends SessionCommand_1.default {
788
947
  }));
789
948
  // 8) Crear el config.json en buildRoot con lo que retorna buildConfig
790
949
  const fullConfig = { config, exercises };
791
- fullConfig.config.slug = fixedSlug;
950
+ fullConfig.config.slug = slug;
792
951
  fullConfig.config.preview =
793
- fixPreviewUrl(fixedSlug, "") ||
952
+ fixPreviewUrl(slug, "") ||
794
953
  "https://raw.githubusercontent.com/learnpack/ide/master/public/learnpack.svg";
795
954
  fs.writeFileSync(path.join(buildRoot, "config.json"), JSON.stringify(fullConfig, null, 2), "utf-8");
796
955
  // 9) Empaquetar en ZIP (solo contenido de buildRoot)
797
- const zipName = `${fixedSlug}.zip`;
956
+ const zipName = `${slug}.zip`;
798
957
  const zipPath = path.join(tmpRoot, zipName);
799
958
  const output = fs.createWriteStream(zipPath);
800
959
  const archive = archiver("zip", { zlib: { level: 9 } });