@learnpack/learnpack 5.0.240 → 5.0.246

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,25 @@ 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.trim() }, courseJson, lang, deployUrl, b64IndexReadme, all_translations);
136
+ all_translations.push(asset.slug);
137
+ }
138
+ };
168
139
  async function startExerciseGeneration(bucket, rigoToken, steps, packageContext, exercise, tutorialDir, courseSlug, purposeSlug, lastLesson = "") {
169
140
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
170
141
  console.log("Starting generation of", exSlug);
@@ -361,7 +332,6 @@ class ServeCommand extends SessionCommand_1.default {
361
332
  app.post("/webhooks/:courseSlug/initial-readme-processor", async (req, res) => {
362
333
  const { courseSlug } = req.params;
363
334
  const body = req.body;
364
- console.log("RECEIVING INITIAL README WEBHOOK", body);
365
335
  // Save the file as courses/courseSlug/README.md
366
336
  const filePath = `courses/${courseSlug}/README.${body.parsed.language_code === "us" ||
367
337
  body.parsed.language_code === "en" ?
@@ -629,19 +599,23 @@ class ServeCommand extends SessionCommand_1.default {
629
599
  });
630
600
  app.post("/actions/translate", express.json(), async (req, res) => {
631
601
  console.log("POST /actions/translate");
632
- const { exerciseSlugs, languages, rigoToken } = req.body;
602
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
633
603
  const query = req.query;
634
604
  const courseSlug = query.slug;
635
- console.log("EXERCISE SLUGS", exerciseSlugs);
636
- console.log("LANGUAGES", languages);
637
- console.log("RIGO TOKEN", rigoToken);
638
605
  if (!rigoToken) {
639
606
  return res.status(400).json({ error: "RigoToken not found" });
640
607
  }
641
608
  const languagesToTranslate = languages.split(",");
609
+ const course = await bucket
610
+ .file(`courses/${courseSlug}/learn.json`)
611
+ .download();
612
+ const courseJson = JSON.parse(course.toString());
613
+ const languageCodes = new Set();
642
614
  try {
643
615
  await Promise.all(exerciseSlugs.map(async (slug) => {
644
- const readmePath = `courses/${courseSlug}/exercises/${slug}/README.md`;
616
+ const readmePath = `courses/${courseSlug}/exercises/${slug}/README${currentLanguage === "us" || currentLanguage === "en" ?
617
+ "" :
618
+ `.${currentLanguage}`}.md`;
645
619
  const readme = await bucket.file(readmePath).download();
646
620
  await Promise.all(languagesToTranslate.map(async (language) => {
647
621
  const response = await (0, rigoActions_1.translateExercise)(rigoToken, {
@@ -649,10 +623,62 @@ class ServeCommand extends SessionCommand_1.default {
649
623
  output_language: language,
650
624
  exercise_slug: slug,
651
625
  });
652
- const translatedReadme = await bucket.file(`courses/${courseSlug}/exercises/${slug}/README.${response.parsed.output_language_code}.md`);
626
+ const translatedReadme = await bucket.file(`courses/${courseSlug}/exercises/${slug}/README${response.parsed.output_language_code === "us" ||
627
+ response.parsed.output_language_code === "en" ?
628
+ "" :
629
+ `.${response.parsed.output_language_code}`}.md`);
653
630
  await translatedReadme.save(response.parsed.translation);
631
+ languageCodes.add(response.parsed.output_language_code);
654
632
  }));
655
633
  }));
634
+ if (languageCodes.has("en")) {
635
+ languageCodes.delete("en");
636
+ languageCodes.add("us");
637
+ }
638
+ const currentLanguages = Object.keys(courseJson.title);
639
+ for (const languageCode of currentLanguages) {
640
+ if (languageCodes.has(languageCode)) {
641
+ languageCodes.delete(languageCode);
642
+ }
643
+ }
644
+ if ([...languageCodes].length > 0) {
645
+ console.log("TRANSLATING COURSE METADATA", languageCodes);
646
+ const translatedCourseMetadata = await Promise.all([...languageCodes].map(async (languageCode) => {
647
+ const result = await (0, rigoActions_1.translateCourseMetadata)(rigoToken, {
648
+ title: courseJson.title[currentLanguage],
649
+ description: courseJson.description[currentLanguage],
650
+ destination_lang_code: languageCode,
651
+ });
652
+ return {
653
+ languageCode,
654
+ title: result.parsed.title,
655
+ description: result.parsed.description,
656
+ };
657
+ }));
658
+ for (const metadata of translatedCourseMetadata) {
659
+ courseJson.title[metadata.languageCode] = metadata.title;
660
+ courseJson.description[metadata.languageCode] =
661
+ metadata.description;
662
+ }
663
+ await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
664
+ const previewReadme = await bucket.file(`courses/${courseSlug}/README${currentLanguage === "us" || currentLanguage === "en" ?
665
+ "" :
666
+ `.${currentLanguage}`}.md`);
667
+ const [previewReadmeContent] = await previewReadme.download();
668
+ const previewReadmeContentString = previewReadmeContent.toString();
669
+ await Promise.all([...languageCodes].map(async (languageCode) => {
670
+ const translatedPreviewReadme = await (0, rigoActions_1.translateExercise)(rigoToken, {
671
+ text_to_translate: previewReadmeContentString,
672
+ output_language: languageCode,
673
+ exercise_slug: "preview-readme",
674
+ });
675
+ await bucket
676
+ .file(`courses/${courseSlug}/README${languageCode === "us" || languageCode === "en" ?
677
+ "" :
678
+ `.${languageCode}`}.md`)
679
+ .save(translatedPreviewReadme.parsed.translation);
680
+ }));
681
+ }
656
682
  return res.status(200).json({ message: "Translated exercises" });
657
683
  }
658
684
  catch (error) {
@@ -661,6 +687,7 @@ class ServeCommand extends SessionCommand_1.default {
661
687
  }
662
688
  });
663
689
  app.delete("/exercise/:slug/delete", async (req, res) => {
690
+ console.log("DELETE /exercise/:slug/delete");
664
691
  const { slug } = req.params;
665
692
  const query = req.query;
666
693
  const courseSlug = query.slug;
@@ -696,53 +723,23 @@ class ServeCommand extends SessionCommand_1.default {
696
723
  }
697
724
  catch (error) {
698
725
  if (error.code === 404) {
699
- console.log("SIDEBAR FILE NOT FOUND", courseSlug);
700
726
  const { exercises } = await (0, configBuilder_1.buildConfig)(bucket, courseSlug);
701
727
  const exerciseSlugsArray = exercises.map(exercise => exercise.slug);
702
728
  const sidebar = await createInitialSidebar(exerciseSlugsArray);
729
+ await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
703
730
  if (rigoToken) {
704
731
  const { fixedSidebar } = await (0, sidebarGenerator_1.checkAndFixSidebarPure)(sidebar, exercises, rigoToken);
705
732
  if (fixedSidebar) {
706
733
  await uploadFileToBucket(bucket, JSON.stringify(fixedSidebar), `courses/${courseSlug}/.learn/sidebar.json`);
707
734
  }
708
- await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
709
735
  return res.status(200).json(fixedSidebar);
710
736
  }
711
- await uploadFileToBucket(bucket, JSON.stringify(sidebar), `courses/${courseSlug}/.learn/sidebar.json`);
712
737
  return res.status(200).json(sidebar);
713
738
  }
714
739
  console.error("Unexpected error:", error);
715
740
  res.status(500).json({ error: error.message });
716
741
  }
717
742
  });
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
743
  app.get("/technologies", async (req, res) => {
747
744
  console.log("GET /technologies");
748
745
  const technologies = await api_1.default.getCurrentTechnologies();
@@ -775,7 +772,7 @@ class ServeCommand extends SessionCommand_1.default {
775
772
  if (!rigoToken || !bcToken) {
776
773
  return res.status(400).json({ error: "Missing tokens" });
777
774
  }
778
- const courseSlug = (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title);
775
+ const courseSlug = syllabus.courseInfo.slug;
779
776
  const tutorialDir = `courses/${courseSlug}`;
780
777
  const learnJson = (0, exports.createLearnJson)(syllabus.courseInfo);
781
778
  try {
@@ -815,7 +812,7 @@ class ServeCommand extends SessionCommand_1.default {
815
812
  await createInitialReadme(JSON.stringify(syllabus.courseInfo), courseSlug, rigoToken);
816
813
  return res.json({
817
814
  message: "Course created",
818
- slug: (0, creatorUtilities_2.slugify)(syllabus.courseInfo.title),
815
+ slug: syllabus.courseInfo.slug,
819
816
  });
820
817
  });
821
818
  app.get("/courses/:courseSlug/syllabus", async (req, res) => {
@@ -889,6 +886,7 @@ class ServeCommand extends SessionCommand_1.default {
889
886
  app.post("/actions/publish/:slug", async (req, res) => {
890
887
  try {
891
888
  const { slug } = req.params;
889
+ const { currentLanguage } = req.body;
892
890
  const rigoToken = req.header("x-rigo-token");
893
891
  const bcToken = req.header("x-breathecode-token");
894
892
  // const { academyId, categoryId } = req.body
@@ -898,7 +896,6 @@ class ServeCommand extends SessionCommand_1.default {
898
896
  .json({ error: "Authentication failed, missing tokens" });
899
897
  }
900
898
  const { config, exercises } = await (0, configBuilder_1.buildConfig)(bucket, slug);
901
- // const fixedSlug = fixSlugLength(slug)
902
899
  const prefix = `courses/${slug}/`;
903
900
  const tmpRoot = path.join(os.tmpdir(), `learnpack-${slug}`);
904
901
  const buildRoot = path.join(tmpRoot, "build");
@@ -931,10 +928,6 @@ class ServeCommand extends SessionCommand_1.default {
931
928
  title = config.title[availableLangs[0]];
932
929
  selectedLang = availableLangs[0];
933
930
  }
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
931
  const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8");
939
932
  const idxHtml = idxTpl
940
933
  .replace(/{{title}}/g, title)
@@ -977,15 +970,9 @@ class ServeCommand extends SessionCommand_1.default {
977
970
  form.append("file", fs.createReadStream(zipPath));
978
971
  form.append("config", JSON.stringify(config));
979
972
  const rigoRes = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/learnpack/upload`, form, {
980
- headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: `Token ${rigoToken.trim()}` }),
973
+ headers: Object.assign(Object.assign({}, form.getHeaders()), { Authorization: "Token " + rigoToken.trim() }),
981
974
  });
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);
975
+ await createMultiLangAsset(bucket, rigoToken, bcToken, slug, fullConfig.config, rigoRes.data.url);
989
976
  rimraf.sync(tmpRoot);
990
977
  console.log("RigoRes", rigoRes.data);
991
978
  return res.json({ url: rigoRes.data.url });