@learnpack/learnpack 5.0.240 → 5.0.244

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.
@@ -2,7 +2,7 @@ import SessionCommand from "../utils/SessionCommand";
2
2
  export declare const handleAssetCreation: (sessionPayload: {
3
3
  token: string;
4
4
  rigobotToken: string;
5
- }, learnJson: any, selectedLang: string, learnpackDeployUrl: string, b64IndexReadme: string) => Promise<void>;
5
+ }, learnJson: any, selectedLang: string, learnpackDeployUrl: string, b64IndexReadme: string, all_translations?: string[]) => Promise<any>;
6
6
  declare class BuildCommand extends SessionCommand {
7
7
  static description: string;
8
8
  static flags: {
@@ -18,8 +18,9 @@ const api_1 = require("../utils/api");
18
18
  const prompts = require("prompts");
19
19
  const rigoActions_1 = require("../utils/rigoActions");
20
20
  const misc_1 = require("../utils/misc");
21
+ const creatorUtilities_1 = require("../utils/creatorUtilities");
21
22
  const uploadZipEndpont = api_1.RIGOBOT_HOST + "/v1/learnpack/upload";
22
- const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme) => {
23
+ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, all_translations = []) => {
23
24
  const categories = {
24
25
  us: 9,
25
26
  es: 10,
@@ -30,11 +31,12 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
30
31
  }
31
32
  try {
32
33
  const user = await api_1.default.validateToken(sessionPayload.token);
33
- const { exists } = await api_1.default.doesAssetExists(sessionPayload.token, learnJson.slug);
34
+ const slug = (0, creatorUtilities_1.slugify)(learnJson.title[selectedLang]).slice(0, 50);
35
+ const { exists } = await api_1.default.doesAssetExists(sessionPayload.token, slug);
34
36
  if (!exists) {
35
37
  console_1.default.info("Asset does not exist in this academy, creating it");
36
38
  const asset = await api_1.default.createAsset(sessionPayload.token, {
37
- slug: learnJson.slug,
39
+ slug: slug,
38
40
  title: learnJson.title[selectedLang],
39
41
  lang: selectedLang,
40
42
  description: learnJson.description[selectedLang],
@@ -46,23 +48,26 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
46
48
  author: user.id,
47
49
  preview: learnJson.preview,
48
50
  readme_raw: b64IndexReadme,
51
+ all_translations,
49
52
  });
50
53
  await api_1.default.updateRigoAssetID(sessionPayload.token, learnJson.slug, asset.id);
51
54
  console_1.default.info("Asset created with id", asset.id);
55
+ return asset;
52
56
  }
53
- else {
54
- console_1.default.info("Asset exists, updating it");
55
- const asset = await api_1.default.updateAsset(sessionPayload.token, learnJson.slug, {
56
- learnpack_deploy_url: learnpackDeployUrl,
57
- title: learnJson.title[selectedLang],
58
- description: learnJson.description[selectedLang],
59
- });
60
- await api_1.default.updateRigoAssetID(sessionPayload.rigobotToken, learnJson.slug, asset.id);
61
- console_1.default.info("Asset updated with id", asset.id);
62
- }
57
+ console_1.default.info("Asset exists, updating it");
58
+ const asset = await api_1.default.updateAsset(sessionPayload.token, slug, {
59
+ learnpack_deploy_url: learnpackDeployUrl,
60
+ title: learnJson.title[selectedLang],
61
+ description: learnJson.description[selectedLang],
62
+ all_translations,
63
+ });
64
+ await api_1.default.updateRigoAssetID(sessionPayload.rigobotToken.trim(), learnJson.slug, asset.id);
65
+ console_1.default.info("Asset updated with id", asset.id);
66
+ return asset;
63
67
  }
64
68
  catch (error) {
65
69
  console_1.default.error("Error updating or creating asset:", error);
70
+ return null;
66
71
  }
67
72
  };
68
73
  exports.handleAssetCreation = handleAssetCreation;
@@ -296,7 +301,7 @@ class BuildCommand extends SessionCommand_1.default {
296
301
  console.log(res.data);
297
302
  fs.unlinkSync(zipFilePath);
298
303
  this.removeDirectory(buildDir);
299
- await (0, exports.handleAssetCreation)(sessionPayload, learnJson, "us", res.data.url, "");
304
+ await (0, exports.handleAssetCreation)(sessionPayload, learnJson, "us", res.data.url, "", []);
300
305
  }
301
306
  catch (error) {
302
307
  if (axios_1.default.isAxiosError(error)) {
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.processImage = exports.createLearnJson = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const command_1 = require("@oclif/command");
6
- // import { readDocument } from "../utils/readDocuments"
7
6
  const youtube_transcript_1 = require("youtube-transcript");
8
7
  const express = require("express");
9
8
  const cors = require("cors");
@@ -42,11 +41,11 @@ function findLast(array, predicate) {
42
41
  }
43
42
  const createLearnJson = (courseInfo) => {
44
43
  // console.log("courseInfo to create learn json", courseInfo)
45
- const expectedPreviewUrl = `https://${(0, creatorUtilities_2.slugify)(courseInfo.title)}.learn-pack.com/preview.png`;
44
+ const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`;
46
45
  console.log("Preview url in generated learn.json", expectedPreviewUrl);
47
46
  const language = courseInfo.language || "en";
48
47
  const learnJson = {
49
- slug: (0, creatorUtilities_2.slugify)(courseInfo.title),
48
+ slug: courseInfo.slug,
50
49
  title: language === "en" || language === "us" ?
51
50
  {
52
51
  us: courseInfo.title,
@@ -72,19 +71,6 @@ const uploadFileToBucket = async (bucket, file, path) => {
72
71
  const fileRef = bucket.file(path);
73
72
  await fileRef.save(Buffer.from(file, "utf8"));
74
73
  };
75
- const uploadImageToBucket = async (bucket, url, path) => {
76
- const response = await fetch(url);
77
- if (!response.ok) {
78
- throw new Error(`Failed to download image: ${response.statusText}`);
79
- }
80
- const contentType = response.headers.get("content-type") || "application/octet-stream";
81
- const buffer = await response.arrayBuffer();
82
- const fileRef = bucket.file(path);
83
- await fileRef.save(Buffer.from(buffer), {
84
- resumable: false,
85
- contentType,
86
- });
87
- };
88
74
  const PARAMS = {
89
75
  expected_grade_level: "8",
90
76
  max_fkgl: 10,
@@ -92,35 +78,6 @@ const PARAMS = {
92
78
  max_rewrite_attempts: 2,
93
79
  max_title_length: 50,
94
80
  };
95
- // app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
96
- // const { courseSlug, imageId } = req.params
97
- // const body = req.body
98
- // console.log("RECEIVING IMAGE WEBHOOK", body)
99
- // const imageUrl = body.image_url
100
- // const imagePath = `courses/${courseSlug}/.learn/assets/${imageId}`
101
- // const imageFile = bucket.file(imagePath)
102
- // const [exists] = await imageFile.exists()
103
- // if (!exists) {
104
- // // Descargar la imagen
105
- // const response = await fetch(imageUrl)
106
- // if (!response.ok) {
107
- // return res.status(400).json({
108
- // status: "ERROR",
109
- // message: "No se pudo descargar la imagen",
110
- // })
111
- // }
112
- // const buffer = await response.arrayBuffer()
113
- // // Guardar la imagen en el bucket
114
- // await imageFile.save(new Uint8Array(buffer), {
115
- // contentType: "image/png",
116
- // })
117
- // }
118
- // emitToNotification(imageId, {
119
- // status: "SUCCESS",
120
- // message: "Image generated successfully",
121
- // })
122
- // res.json({ status: "SUCCESS" })
123
- // })
124
81
  const processImage = async (url, description, rigoToken, courseSlug) => {
125
82
  try {
126
83
  // TODO: MAKE THIS ASYNC
@@ -130,7 +87,6 @@ const processImage = async (url, description, rigoToken, courseSlug) => {
130
87
  prompt: description,
131
88
  callbackUrl: webhookUrl,
132
89
  });
133
- // await uploadImageToBucket(bucket, res.image_url, imagePath)
134
90
  // console.log("✅ Image", imagePath, "generated successfully!")
135
91
  return true;
136
92
  }
@@ -161,10 +117,31 @@ const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) =>
161
117
  await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
162
118
  };
163
119
  const cleanFormState = (formState) => {
164
- // keysToDelete: description, technologies, purpose
165
120
  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"]);
166
121
  return rest;
167
122
  };
123
+ const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, courseJson, deployUrl) => {
124
+ const availableLangs = Object.keys(courseJson.title);
125
+ console.log("AVAILABLE LANGUAGES to upload asset", availableLangs);
126
+ const all_translations = [];
127
+ for (const lang of availableLangs) {
128
+ // eslint-disable-next-line no-await-in-loop
129
+ const indexReadme = await bucket.file(`courses/${courseSlug}/README.${lang === "us" || lang === "en" ? "md" : `${lang}.md`}`);
130
+ // eslint-disable-next-line no-await-in-loop
131
+ const [indexReadmeContent] = await indexReadme.download();
132
+ const indexReadmeString = indexReadmeContent.toString();
133
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
134
+ // eslint-disable-next-line no-await-in-loop
135
+ const asset = await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken }, courseJson, lang, deployUrl, b64IndexReadme, all_translations);
136
+ all_translations.push(asset.slug);
137
+ }
138
+ // const languageCodes = new Set(languageCodes)
139
+ // const asset = await api.createAsset(rigoToken, {
140
+ // slug: courseSlug,
141
+ // title: courseJson.title,
142
+ // description: courseJson.description,
143
+ // })
144
+ };
168
145
  async function startExerciseGeneration(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
169
146
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
170
147
  console.log("Starting generation of", exSlug);
@@ -361,7 +338,6 @@ class ServeCommand extends SessionCommand_1.default {
361
338
  app.post("/webhooks/:courseSlug/initial-readme-processor", async (req, res) => {
362
339
  const { courseSlug } = req.params;
363
340
  const body = req.body;
364
- console.log("RECEIVING INITIAL README WEBHOOK", body);
365
341
  // Save the file as courses/courseSlug/README.md
366
342
  const filePath = `courses/${courseSlug}/README.${body.parsed.language_code === "us" ||
367
343
  body.parsed.language_code === "en" ?
@@ -629,19 +605,23 @@ class ServeCommand extends SessionCommand_1.default {
629
605
  });
630
606
  app.post("/actions/translate", express.json(), async (req, res) => {
631
607
  console.log("POST /actions/translate");
632
- const { exerciseSlugs, languages, rigoToken } = req.body;
608
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
633
609
  const query = req.query;
634
610
  const courseSlug = query.slug;
635
- console.log("EXERCISE SLUGS", exerciseSlugs);
636
- console.log("LANGUAGES", languages);
637
- console.log("RIGO TOKEN", rigoToken);
638
611
  if (!rigoToken) {
639
612
  return res.status(400).json({ error: "RigoToken not found" });
640
613
  }
641
614
  const languagesToTranslate = languages.split(",");
615
+ const course = await bucket
616
+ .file(`courses/${courseSlug}/learn.json`)
617
+ .download();
618
+ const courseJson = JSON.parse(course.toString());
619
+ const languageCodes = new Set();
642
620
  try {
643
621
  await Promise.all(exerciseSlugs.map(async (slug) => {
644
- const readmePath = `courses/${courseSlug}/exercises/${slug}/README.md`;
622
+ const readmePath = `courses/${courseSlug}/exercises/${slug}/README${currentLanguage === "us" || currentLanguage === "en" ?
623
+ "" :
624
+ `.${currentLanguage}`}.md`;
645
625
  const readme = await bucket.file(readmePath).download();
646
626
  await Promise.all(languagesToTranslate.map(async (language) => {
647
627
  const response = await (0, rigoActions_1.translateExercise)(rigoToken, {
@@ -649,10 +629,62 @@ class ServeCommand extends SessionCommand_1.default {
649
629
  output_language: language,
650
630
  exercise_slug: slug,
651
631
  });
652
- const translatedReadme = await bucket.file(`courses/${courseSlug}/exercises/${slug}/README.${response.parsed.output_language_code}.md`);
632
+ const translatedReadme = await bucket.file(`courses/${courseSlug}/exercises/${slug}/README${response.parsed.output_language_code === "us" ||
633
+ response.parsed.output_language_code === "en" ?
634
+ "" :
635
+ `.${response.parsed.output_language_code}`}.md`);
653
636
  await translatedReadme.save(response.parsed.translation);
637
+ languageCodes.add(response.parsed.output_language_code);
654
638
  }));
655
639
  }));
640
+ if (languageCodes.has("en")) {
641
+ languageCodes.delete("en");
642
+ languageCodes.add("us");
643
+ }
644
+ const currentLanguages = Object.keys(courseJson.title);
645
+ for (const languageCode of currentLanguages) {
646
+ if (languageCodes.has(languageCode)) {
647
+ languageCodes.delete(languageCode);
648
+ }
649
+ }
650
+ if ([...languageCodes].length > 0) {
651
+ console.log("TRANSLATING COURSE METADATA", languageCodes);
652
+ const translatedCourseMetadata = await Promise.all([...languageCodes].map(async (languageCode) => {
653
+ const result = await (0, rigoActions_1.translateCourseMetadata)(rigoToken, {
654
+ title: courseJson.title[currentLanguage],
655
+ description: courseJson.description[currentLanguage],
656
+ destination_lang_code: languageCode,
657
+ });
658
+ return {
659
+ languageCode,
660
+ title: result.parsed.title,
661
+ description: result.parsed.description,
662
+ };
663
+ }));
664
+ for (const metadata of translatedCourseMetadata) {
665
+ courseJson.title[metadata.languageCode] = metadata.title;
666
+ courseJson.description[metadata.languageCode] =
667
+ metadata.description;
668
+ }
669
+ await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
670
+ const previewReadme = await bucket.file(`courses/${courseSlug}/README${currentLanguage === "us" || currentLanguage === "en" ?
671
+ "" :
672
+ `.${currentLanguage}`}.md`);
673
+ const [previewReadmeContent] = await previewReadme.download();
674
+ const previewReadmeContentString = previewReadmeContent.toString();
675
+ await Promise.all([...languageCodes].map(async (languageCode) => {
676
+ const translatedPreviewReadme = await (0, rigoActions_1.translateExercise)(rigoToken, {
677
+ text_to_translate: previewReadmeContentString,
678
+ output_language: languageCode,
679
+ exercise_slug: "preview-readme",
680
+ });
681
+ await bucket
682
+ .file(`courses/${courseSlug}/README${languageCode === "us" || languageCode === "en" ?
683
+ "" :
684
+ `.${languageCode}`}.md`)
685
+ .save(translatedPreviewReadme.parsed.translation);
686
+ }));
687
+ }
656
688
  return res.status(200).json({ message: "Translated exercises" });
657
689
  }
658
690
  catch (error) {
@@ -661,6 +693,7 @@ class ServeCommand extends SessionCommand_1.default {
661
693
  }
662
694
  });
663
695
  app.delete("/exercise/:slug/delete", async (req, res) => {
696
+ console.log("DELETE /exercise/:slug/delete");
664
697
  const { slug } = req.params;
665
698
  const query = req.query;
666
699
  const courseSlug = query.slug;
@@ -696,53 +729,23 @@ class ServeCommand extends SessionCommand_1.default {
696
729
  }
697
730
  catch (error) {
698
731
  if (error.code === 404) {
699
- console.log("SIDEBAR FILE NOT FOUND", courseSlug);
700
732
  const { exercises } = await (0, configBuilder_1.buildConfig)(bucket, courseSlug);
701
733
  const exerciseSlugsArray = exercises.map(exercise => exercise.slug);
702
734
  const sidebar = await createInitialSidebar(exerciseSlugsArray);
735
+ await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
703
736
  if (rigoToken) {
704
737
  const { fixedSidebar } = await (0, sidebarGenerator_1.checkAndFixSidebarPure)(sidebar, exercises, rigoToken);
705
738
  if (fixedSidebar) {
706
739
  await uploadFileToBucket(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
707
740
  }
708
- await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
709
741
  return res.status(200).json(fixedSidebar);
710
742
  }
711
- await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
712
743
  return res.status(200).json(sidebar);
713
744
  }
714
745
  console.error("Unexpected error:", error);
715
746
  res.status(500).json({ error: error.message });
716
747
  }
717
748
  });
718
- // app.get("/test/:slug", (req, res) => {
719
- // emitToCourse(req.params.slug, "course-creation", {
720
- // lesson: "000-welcome-to-bird-domestication",
721
- // status: "generating",
722
- // log: "Hello",
723
- // })
724
- // emitToCourse(req.params.slug, "course-creation", {
725
- // lesson: "000-welcome-to-bird-domestication",
726
- // status: "generating",
727
- // log: "Hello",
728
- // })
729
- // emitToCourse(req.params.slug, "course-creation", {
730
- // lesson: "000-welcome-to-bird-domestication",
731
- // status: "generating",
732
- // log: "Hello broder",
733
- // })
734
- // emitToCourse(req.params.slug, "course-creation", {
735
- // lesson: "000-welcome-to-bird-domestication",
736
- // status: "done",
737
- // log: "Hello broder",
738
- // })
739
- // emitToCourse(req.params.slug, "course-creation", {
740
- // lesson: "other",
741
- // status: "generating",
742
- // log: "Hello",
743
- // })
744
- // res.send({ message: "Course creation started" })
745
- // })
746
749
  app.get("/technologies", async (req, res) => {
747
750
  console.log("GET /technologies");
748
751
  const technologies = await api_1.default.getCurrentTechnologies();
@@ -775,7 +778,7 @@ class ServeCommand extends SessionCommand_1.default {
775
778
  if (!rigoToken || !bcToken) {
776
779
  return res.status(400).json({ error: "Missing tokens" });
777
780
  }
778
- const courseSlug = (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title);
781
+ const courseSlug = syllabus.courseInfo.slug;
779
782
  const tutorialDir = `courses/${courseSlug}`;
780
783
  const learnJson = (0, exports.createLearnJson)(syllabus.courseInfo);
781
784
  try {
@@ -815,7 +818,7 @@ class ServeCommand extends SessionCommand_1.default {
815
818
  await createInitialReadme(JSON.stringify(syllabus.courseInfo), courseSlug, rigoToken);
816
819
  return res.json({
817
820
  message: "Course created",
818
- slug: (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title),
821
+ slug: syllabus.courseInfo.slug,
819
822
  });
820
823
  });
821
824
  app.get("/courses/:courseSlug/syllabus", async (req, res) => {
@@ -931,10 +934,6 @@ class ServeCommand extends SessionCommand_1.default {
931
934
  title = config.title[availableLangs[0]];
932
935
  selectedLang = availableLangs[0];
933
936
  }
934
- // console.log(availableLangs, "AVAILABLE LANGs")
935
- // console.log(selectedLang, "SELECTED LANG")
936
- // console.log(title, "TITLE")
937
- // 5) Inyectar placeholders en index.html
938
937
  const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8");
939
938
  const idxHtml = idxTpl
940
939
  .replace(/{{title}}/g, title)
@@ -979,13 +978,7 @@ class ServeCommand extends SessionCommand_1.default {
979
978
  const rigoRes = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/learnpack/upload`, form, {
980
979
  headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Token ${rigoToken.trim()}` }),
981
980
  });
982
- const indexReadme = await bucket.file(`courses/${slug}/README.${selectedLang === "us" || selectedLang === "en" ?
983
- "md" :
984
- `${selectedLang}.md`}`);
985
- const [indexReadmeContent] = await indexReadme.download();
986
- const indexReadmeString = indexReadmeContent.toString();
987
- const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
988
- await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken }, fullConfig.config, selectedLang, rigoRes.data.url, b64IndexReadme);
981
+ await createMultiLangAsset(bucket, rigoToken, bcToken, slug, fullConfig.config, rigoRes.data.url);
989
982
  rimraf.sync(tmpRoot);
990
983
  console.log("RigoRes", rigoRes.data);
991
984
  return res.json({ url: rigoRes.data.url });