@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.
@@ -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-DJn8b8wj.js"></script>
13
+ <script type="module" crossorigin src="/creator/assets/index-BWHp9KF3.js"></script>
14
14
  <link rel="stylesheet" crossorigin href="/creator/assets/index-DmpsXknz.css">
15
15
  </head>
16
16
  <body>
@@ -23,6 +23,7 @@ export type FormState = {
23
23
  currentStep: string;
24
24
  title: string;
25
25
  purpose: string;
26
+ slug: string;
26
27
  };
27
28
  export type Syllabus = {
28
29
  lessons: Lesson[];
@@ -23,6 +23,7 @@ type TAssetMissing = {
23
23
  author: number;
24
24
  preview: string;
25
25
  readme_raw: string;
26
+ all_translations: string[];
26
27
  };
27
28
  export declare const createAsset: (token: string, asset: TAssetMissing) => Promise<any>;
28
29
  export declare const doesAssetExists: (token: string, assetSlug: string) => Promise<{
@@ -1,5 +1,5 @@
1
1
  import { Server as SocketIOServer } from "socket.io";
2
2
  export declare function initSocketIO(server: any): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
3
3
  export declare function emitToCourse(courseSlug: string, event: string, payload: any): void;
4
- export declare function emitToNotification(notificationId: string, payload: any): void;
4
+ export declare function emitToNotification(notificationId: string, payload: any, retry?: number): void;
5
5
  export declare function getSocketIO(): SocketIOServer<import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, import("socket.io/dist/typed-events").DefaultEventsMap, any>;
@@ -62,11 +62,18 @@ function emitToCourse(courseSlug, event, payload) {
62
62
  socket.emit(event, payload);
63
63
  }
64
64
  }
65
- function emitToNotification(notificationId, payload) {
66
- console.log("Emitting to notification", notificationId, payload);
65
+ function emitToNotification(notificationId, payload, retry = 0) {
67
66
  const socketIds = notificationSocketMap.get(notificationId);
68
- if (!socketIds || socketIds.size === 0)
67
+ if (!socketIds || socketIds.size === 0) {
68
+ if (retry > 3) {
69
+ console.log("❌ Notification", notificationId, "not found");
70
+ return;
71
+ }
72
+ setTimeout(() => {
73
+ emitToNotification(notificationId, payload, retry + 1);
74
+ }, 3000);
69
75
  return;
76
+ }
70
77
  for (const id of socketIds) {
71
78
  const socket = socketStore.get(id);
72
79
  if (socket)
@@ -60,6 +60,11 @@ type TCreateStructuredPreviewReadmeInputs = {
60
60
  tutorial_info: string;
61
61
  };
62
62
  export declare const createStructuredPreviewReadme: (token: string, inputs: TCreateStructuredPreviewReadmeInputs, webhookUrl?: string) => Promise<any>;
63
+ export declare const translateCourseMetadata: (token: string, inputs: {
64
+ title: string;
65
+ description: string;
66
+ destination_lang_code: string;
67
+ }) => Promise<any>;
63
68
  export declare function createPreviewReadme(tutorialDir: string, packageInfo: PackageInfo, rigoToken: string, readmeContents: string[]): Promise<void>;
64
69
  type TReduceReadmeInputs = {
65
70
  lesson: string;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
3
+ exports.isPackageAuthor = exports.fillSidebarJSON = exports.generateCourseShortName = exports.isValidRigoToken = exports.translateCourseMetadata = exports.createStructuredPreviewReadme = exports.readmeCreator = exports.createCodingReadme = exports.createCodeFile = exports.interactiveCreation = exports.generateCourseIntroduction = exports.translateExercise = exports.generateImage = exports.hasCreatorPermission = exports.createReadme = void 0;
4
4
  exports.downloadImage = downloadImage;
5
5
  exports.createPreviewReadme = createPreviewReadme;
6
6
  exports.makeReadmeReadable = makeReadmeReadable;
@@ -191,6 +191,16 @@ const createStructuredPreviewReadme = async (token, inputs, webhookUrl) => {
191
191
  return response.data;
192
192
  };
193
193
  exports.createStructuredPreviewReadme = createStructuredPreviewReadme;
194
+ const translateCourseMetadata = async (token, inputs) => {
195
+ const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/translate-course-metadata/`, { inputs, include_purpose_objective: false, execute_async: false }, {
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ Authorization: "Token " + token,
199
+ },
200
+ });
201
+ return response.data;
202
+ };
203
+ exports.translateCourseMetadata = translateCourseMetadata;
194
204
  async function createPreviewReadme(tutorialDir, packageInfo, rigoToken, readmeContents) {
195
205
  const readmeFilename = `README.md`;
196
206
  const readmeContent = await (0, exports.generateCourseIntroduction)(rigoToken, {
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.240",
4
+ "version": "5.0.244",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -19,6 +19,7 @@ import api, { getConsumable, RIGOBOT_HOST, TAcademy } from "../utils/api"
19
19
  import * as prompts from "prompts"
20
20
  import { isValidRigoToken } from "../utils/rigoActions"
21
21
  import { minutesToISO8601Duration } from "../utils/misc"
22
+ import { slugify } from "../utils/creatorUtilities"
22
23
 
23
24
  const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
24
25
 
@@ -27,7 +28,8 @@ export const handleAssetCreation = async (
27
28
  learnJson: any,
28
29
  selectedLang: string,
29
30
  learnpackDeployUrl: string,
30
- b64IndexReadme: string
31
+ b64IndexReadme: string,
32
+ all_translations: string[] = []
31
33
  ) => {
32
34
  const categories: Record<string, number> = {
33
35
  us: 9,
@@ -43,15 +45,13 @@ export const handleAssetCreation = async (
43
45
  try {
44
46
  const user = await api.validateToken(sessionPayload.token)
45
47
 
46
- const { exists } = await api.doesAssetExists(
47
- sessionPayload.token,
48
- learnJson.slug
49
- )
48
+ const slug = slugify(learnJson.title[selectedLang]).slice(0, 50)
49
+ const { exists } = await api.doesAssetExists(sessionPayload.token, slug)
50
50
 
51
51
  if (!exists) {
52
52
  Console.info("Asset does not exist in this academy, creating it")
53
53
  const asset = await api.createAsset(sessionPayload.token, {
54
- slug: learnJson.slug,
54
+ slug: slug,
55
55
  title: learnJson.title[selectedLang],
56
56
  lang: selectedLang,
57
57
  description: learnJson.description[selectedLang],
@@ -63,6 +63,7 @@ export const handleAssetCreation = async (
63
63
  author: user.id,
64
64
  preview: learnJson.preview,
65
65
  readme_raw: b64IndexReadme,
66
+ all_translations,
66
67
  })
67
68
  await api.updateRigoAssetID(
68
69
  sessionPayload.token,
@@ -70,26 +71,26 @@ export const handleAssetCreation = async (
70
71
  asset.id
71
72
  )
72
73
  Console.info("Asset created with id", asset.id)
73
- } else {
74
- Console.info("Asset exists, updating it")
75
- const asset = await api.updateAsset(
76
- sessionPayload.token,
77
- learnJson.slug,
78
- {
79
- learnpack_deploy_url: learnpackDeployUrl,
80
- title: learnJson.title[selectedLang],
81
- description: learnJson.description[selectedLang],
82
- }
83
- )
84
- await api.updateRigoAssetID(
85
- sessionPayload.rigobotToken,
86
- learnJson.slug,
87
- asset.id
88
- )
89
- Console.info("Asset updated with id", asset.id)
74
+ return asset
90
75
  }
76
+
77
+ Console.info("Asset exists, updating it")
78
+ const asset = await api.updateAsset(sessionPayload.token, slug, {
79
+ learnpack_deploy_url: learnpackDeployUrl,
80
+ title: learnJson.title[selectedLang],
81
+ description: learnJson.description[selectedLang],
82
+ all_translations,
83
+ })
84
+ await api.updateRigoAssetID(
85
+ sessionPayload.rigobotToken.trim(),
86
+ learnJson.slug,
87
+ asset.id
88
+ )
89
+ Console.info("Asset updated with id", asset.id)
90
+ return asset
91
91
  } catch (error) {
92
92
  Console.error("Error updating or creating asset:", error)
93
+ return null
93
94
  }
94
95
  }
95
96
 
@@ -442,7 +443,8 @@ class BuildCommand extends SessionCommand {
442
443
  learnJson,
443
444
  "us",
444
445
  res.data.url,
445
- ""
446
+ "",
447
+ []
446
448
  )
447
449
  } catch (error) {
448
450
  if (axios.isAxiosError(error)) {
@@ -1,6 +1,5 @@
1
1
  import { flags } from "@oclif/command"
2
2
 
3
- // import { readDocument } from "../utils/readDocuments"
4
3
  import { YoutubeTranscript } from "youtube-transcript"
5
4
  import * as express from "express"
6
5
  import * as cors from "cors"
@@ -31,6 +30,7 @@ import {
31
30
  generateImage,
32
31
  isPackageAuthor,
33
32
  createStructuredPreviewReadme,
33
+ translateCourseMetadata,
34
34
  } from "../utils/rigoActions"
35
35
  import * as dotenv from "dotenv"
36
36
  import {
@@ -65,15 +65,13 @@ function findLast<T>(
65
65
  export const createLearnJson = (courseInfo: FormState) => {
66
66
  // console.log("courseInfo to create learn json", courseInfo)
67
67
 
68
- const expectedPreviewUrl = `https://${slugify(
69
- courseInfo.title as string
70
- )}.learn-pack.com/preview.png`
68
+ const expectedPreviewUrl = `https://${courseInfo.slug}.learn-pack.com/preview.png`
71
69
  console.log("Preview url in generated learn.json", expectedPreviewUrl)
72
70
 
73
71
  const language = courseInfo.language || "en"
74
72
 
75
73
  const learnJson = {
76
- slug: slugify(courseInfo.title as string),
74
+ slug: courseInfo.slug,
77
75
  title:
78
76
  language === "en" || language === "us" ?
79
77
  {
@@ -106,27 +104,6 @@ const uploadFileToBucket = async (
106
104
  await fileRef.save(Buffer.from(file, "utf8"))
107
105
  }
108
106
 
109
- const uploadImageToBucket = async (
110
- bucket: Bucket,
111
- url: string,
112
- path: string
113
- ) => {
114
- const response = await fetch(url)
115
- if (!response.ok) {
116
- throw new Error(`Failed to download image: ${response.statusText}`)
117
- }
118
-
119
- const contentType =
120
- response.headers.get("content-type") || "application/octet-stream"
121
- const buffer = await response.arrayBuffer()
122
-
123
- const fileRef = bucket.file(path)
124
- await fileRef.save(Buffer.from(buffer), {
125
- resumable: false,
126
- contentType,
127
- })
128
- }
129
-
130
107
  const PARAMS = {
131
108
  expected_grade_level: "8",
132
109
  max_fkgl: 10,
@@ -135,41 +112,6 @@ const PARAMS = {
135
112
  max_title_length: 50,
136
113
  }
137
114
 
138
- // app.post("/webhooks/:courseSlug/images/:imageId", async (req, res) => {
139
- // const { courseSlug, imageId } = req.params
140
- // const body = req.body
141
- // console.log("RECEIVING IMAGE WEBHOOK", body)
142
-
143
- // const imageUrl = body.image_url
144
- // const imagePath = `courses/${courseSlug}/.learn/assets/${imageId}`
145
-
146
- // const imageFile = bucket.file(imagePath)
147
- // const [exists] = await imageFile.exists()
148
- // if (!exists) {
149
- // // Descargar la imagen
150
- // const response = await fetch(imageUrl)
151
- // if (!response.ok) {
152
- // return res.status(400).json({
153
- // status: "ERROR",
154
- // message: "No se pudo descargar la imagen",
155
- // })
156
- // }
157
-
158
- // const buffer = await response.arrayBuffer()
159
-
160
- // // Guardar la imagen en el bucket
161
- // await imageFile.save(new Uint8Array(buffer), {
162
- // contentType: "image/png",
163
- // })
164
- // }
165
-
166
- // emitToNotification(imageId, {
167
- // status: "SUCCESS",
168
- // message: "Image generated successfully",
169
- // })
170
- // res.json({ status: "SUCCESS" })
171
- // })
172
-
173
115
  export const processImage = async (
174
116
  url: string,
175
117
  description: string,
@@ -186,7 +128,6 @@ export const processImage = async (
186
128
  prompt: description,
187
129
  callbackUrl: webhookUrl,
188
130
  })
189
- // await uploadImageToBucket(bucket, res.image_url, imagePath)
190
131
 
191
132
  // console.log("✅ Image", imagePath, "generated successfully!")
192
133
  return true
@@ -236,7 +177,6 @@ const uploadInitialReadme = async (
236
177
  }
237
178
 
238
179
  const cleanFormState = (formState: FormState) => {
239
- // keysToDelete: description, technologies, purpose
240
180
  const {
241
181
  description,
242
182
  technologies,
@@ -252,6 +192,51 @@ const cleanFormState = (formState: FormState) => {
252
192
  return rest
253
193
  }
254
194
 
195
+ const createMultiLangAsset = async (
196
+ bucket: Bucket,
197
+ rigoToken: string,
198
+ bcToken: string,
199
+ courseSlug: string,
200
+ courseJson: any,
201
+ deployUrl: string
202
+ ) => {
203
+ const availableLangs = Object.keys(courseJson.title)
204
+ console.log("AVAILABLE LANGUAGES to upload asset", availableLangs)
205
+
206
+ const all_translations: string[] = []
207
+
208
+ for (const lang of availableLangs) {
209
+ // eslint-disable-next-line no-await-in-loop
210
+ const indexReadme = await bucket.file(
211
+ `courses/${courseSlug}/README.${
212
+ lang === "us" || lang === "en" ? "md" : `${lang}.md`
213
+ }`
214
+ )
215
+ // eslint-disable-next-line no-await-in-loop
216
+ const [indexReadmeContent] = await indexReadme.download()
217
+ const indexReadmeString = indexReadmeContent.toString()
218
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
219
+
220
+ // eslint-disable-next-line no-await-in-loop
221
+ const asset = await handleAssetCreation(
222
+ { token: bcToken, rigobotToken: rigoToken },
223
+ courseJson,
224
+ lang,
225
+ deployUrl,
226
+ b64IndexReadme,
227
+ all_translations
228
+ )
229
+
230
+ all_translations.push(asset.slug)
231
+ }
232
+ // const languageCodes = new Set(languageCodes)
233
+ // const asset = await api.createAsset(rigoToken, {
234
+ // slug: courseSlug,
235
+ // title: courseJson.title,
236
+ // description: courseJson.description,
237
+ // })
238
+ }
239
+
255
240
  async function startExerciseGeneration(
256
241
  bucket: Bucket,
257
242
  rigoToken: string,
@@ -544,7 +529,6 @@ export default class ServeCommand extends SessionCommand {
544
529
  const { courseSlug } = req.params
545
530
  const body = req.body
546
531
 
547
- console.log("RECEIVING INITIAL README WEBHOOK", body)
548
532
  // Save the file as courses/courseSlug/README.md
549
533
  const filePath = `courses/${courseSlug}/README.${
550
534
  body.parsed.language_code === "us" ||
@@ -935,25 +919,34 @@ export default class ServeCommand extends SessionCommand {
935
919
 
936
920
  app.post("/actions/translate", express.json(), async (req, res) => {
937
921
  console.log("POST /actions/translate")
938
- const { exerciseSlugs, languages, rigoToken } = req.body
922
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body
939
923
  const query = req.query
940
924
  const courseSlug = query.slug
941
925
 
942
- console.log("EXERCISE SLUGS", exerciseSlugs)
943
- console.log("LANGUAGES", languages)
944
- console.log("RIGO TOKEN", rigoToken)
945
-
946
926
  if (!rigoToken) {
947
927
  return res.status(400).json({ error: "RigoToken not found" })
948
928
  }
949
929
 
950
930
  const languagesToTranslate: string[] = languages.split(",")
951
931
 
932
+ const course = await bucket
933
+ .file(`courses/${courseSlug}/learn.json`)
934
+ .download()
935
+
936
+ const courseJson = JSON.parse(course.toString())
937
+ const languageCodes = new Set()
938
+
952
939
  try {
953
940
  await Promise.all(
954
941
  exerciseSlugs.map(async (slug: string) => {
955
- const readmePath = `courses/${courseSlug}/exercises/${slug}/README.md`
942
+ const readmePath = `courses/${courseSlug}/exercises/${slug}/README${
943
+ currentLanguage === "us" || currentLanguage === "en" ?
944
+ "" :
945
+ `.${currentLanguage}`
946
+ }.md`
947
+
956
948
  const readme = await bucket.file(readmePath).download()
949
+
957
950
  await Promise.all(
958
951
  languagesToTranslate.map(async (language: string) => {
959
952
  const response = await translateExercise(rigoToken, {
@@ -963,14 +956,98 @@ export default class ServeCommand extends SessionCommand {
963
956
  })
964
957
 
965
958
  const translatedReadme = await bucket.file(
966
- `courses/${courseSlug}/exercises/${slug}/README.${response.parsed.output_language_code}.md`
959
+ `courses/${courseSlug}/exercises/${slug}/README${
960
+ response.parsed.output_language_code === "us" ||
961
+ response.parsed.output_language_code === "en" ?
962
+ "" :
963
+ `.${response.parsed.output_language_code}`
964
+ }.md`
967
965
  )
968
966
  await translatedReadme.save(response.parsed.translation)
967
+
968
+ languageCodes.add(response.parsed.output_language_code)
969
969
  })
970
970
  )
971
971
  })
972
972
  )
973
973
 
974
+ if (languageCodes.has("en")) {
975
+ languageCodes.delete("en")
976
+ languageCodes.add("us")
977
+ }
978
+
979
+ const currentLanguages = Object.keys(courseJson.title)
980
+ for (const languageCode of currentLanguages) {
981
+ if (languageCodes.has(languageCode)) {
982
+ languageCodes.delete(languageCode)
983
+ }
984
+ }
985
+
986
+ if ([...languageCodes].length > 0) {
987
+ console.log("TRANSLATING COURSE METADATA", languageCodes)
988
+
989
+ const translatedCourseMetadata = await Promise.all(
990
+ [...languageCodes].map(async languageCode => {
991
+ const result = await translateCourseMetadata(rigoToken, {
992
+ title: courseJson.title[currentLanguage],
993
+ description: courseJson.description[currentLanguage],
994
+ destination_lang_code: languageCode as string,
995
+ })
996
+
997
+ return {
998
+ languageCode,
999
+ title: result.parsed.title,
1000
+ description: result.parsed.description,
1001
+ }
1002
+ })
1003
+ )
1004
+
1005
+ for (const metadata of translatedCourseMetadata) {
1006
+ courseJson.title[metadata.languageCode as string] = metadata.title
1007
+ courseJson.description[metadata.languageCode as string] =
1008
+ metadata.description
1009
+ }
1010
+
1011
+ await uploadFileToBucket(
1012
+ bucket,
1013
+ JSON.stringify(courseJson),
1014
+ `courses/${courseSlug}/learn.json`
1015
+ )
1016
+
1017
+ const previewReadme = await bucket.file(
1018
+ `courses/${courseSlug}/README${
1019
+ currentLanguage === "us" || currentLanguage === "en" ?
1020
+ "" :
1021
+ `.${currentLanguage}`
1022
+ }.md`
1023
+ )
1024
+ const [previewReadmeContent] = await previewReadme.download()
1025
+ const previewReadmeContentString = previewReadmeContent.toString()
1026
+
1027
+ await Promise.all(
1028
+ [...languageCodes].map(async languageCode => {
1029
+ const translatedPreviewReadme = await translateExercise(
1030
+ rigoToken,
1031
+ {
1032
+ text_to_translate: previewReadmeContentString,
1033
+ output_language: languageCode as string,
1034
+ exercise_slug: "preview-readme",
1035
+ }
1036
+ )
1037
+
1038
+ await bucket
1039
+ .file(
1040
+ `courses/${courseSlug}/README${
1041
+ languageCode === "us" || languageCode === "en" ?
1042
+ "" :
1043
+ `.${languageCode}`
1044
+ }.md`
1045
+ )
1046
+ .save(translatedPreviewReadme.parsed.translation)
1047
+ })
1048
+ )
1049
+ }
1050
+
974
1051
  return res.status(200).json({ message: "Translated exercises" })
975
1052
  } catch (error) {
976
1053
  console.log(error, "ERROR")
@@ -979,6 +1056,8 @@ export default class ServeCommand extends SessionCommand {
979
1056
  })
980
1057
 
981
1058
  app.delete("/exercise/:slug/delete", async (req, res) => {
1059
+ console.log("DELETE /exercise/:slug/delete")
1060
+
982
1061
  const { slug } = req.params
983
1062
  const query = req.query
984
1063
  const courseSlug = query.slug
@@ -1032,13 +1111,17 @@ export default class ServeCommand extends SessionCommand {
1032
1111
  res.json(sidebar)
1033
1112
  } catch (error: any) {
1034
1113
  if (error.code === 404) {
1035
- console.log("SIDEBAR FILE NOT FOUND", courseSlug)
1036
-
1037
1114
  const { exercises } = await buildConfig(bucket, courseSlug)
1038
1115
 
1039
1116
  const exerciseSlugsArray = exercises.map(exercise => exercise.slug)
1040
1117
  const sidebar = await createInitialSidebar(exerciseSlugsArray)
1041
1118
 
1119
+ await uploadFileToBucket(
1120
+ bucket,
1121
+ JSON.stringify(sidebar),
1122
+ `courses/${courseSlug}/.learn/sidebar.json`
1123
+ )
1124
+
1042
1125
  if (rigoToken) {
1043
1126
  const { fixedSidebar } = await checkAndFixSidebarPure(
1044
1127
  sidebar,
@@ -1053,20 +1136,9 @@ export default class ServeCommand extends SessionCommand {
1053
1136
  )
1054
1137
  }
1055
1138
 
1056
- await uploadFileToBucket(
1057
- bucket,
1058
- JSON.stringify(sidebar),
1059
- `courses/${courseSlug}/.learn/sidebar.json`
1060
- )
1061
1139
  return res.status(200).json(fixedSidebar)
1062
1140
  }
1063
1141
 
1064
- await uploadFileToBucket(
1065
- bucket,
1066
- JSON.stringify(sidebar),
1067
- `courses/${courseSlug}/.learn/sidebar.json`
1068
- )
1069
-
1070
1142
  return res.status(200).json(sidebar)
1071
1143
  }
1072
1144
 
@@ -1075,35 +1147,6 @@ export default class ServeCommand extends SessionCommand {
1075
1147
  }
1076
1148
  })
1077
1149
 
1078
- // app.get("/test/:slug", (req, res) => {
1079
- // emitToCourse(req.params.slug, "course-creation", {
1080
- // lesson: "000-welcome-to-bird-domestication",
1081
- // status: "generating",
1082
- // log: "Hello",
1083
- // })
1084
- // emitToCourse(req.params.slug, "course-creation", {
1085
- // lesson: "000-welcome-to-bird-domestication",
1086
- // status: "generating",
1087
- // log: "Hello",
1088
- // })
1089
- // emitToCourse(req.params.slug, "course-creation", {
1090
- // lesson: "000-welcome-to-bird-domestication",
1091
- // status: "generating",
1092
- // log: "Hello broder",
1093
- // })
1094
- // emitToCourse(req.params.slug, "course-creation", {
1095
- // lesson: "000-welcome-to-bird-domestication",
1096
- // status: "done",
1097
- // log: "Hello broder",
1098
- // })
1099
- // emitToCourse(req.params.slug, "course-creation", {
1100
- // lesson: "other",
1101
- // status: "generating",
1102
- // log: "Hello",
1103
- // })
1104
- // res.send({ message: "Course creation started" })
1105
- // })
1106
-
1107
1150
  app.get("/technologies", async (req, res) => {
1108
1151
  console.log("GET /technologies")
1109
1152
 
@@ -1143,7 +1186,7 @@ export default class ServeCommand extends SessionCommand {
1143
1186
  return res.status(400).json({ error: "Missing tokens" })
1144
1187
  }
1145
1188
 
1146
- const courseSlug = slugify(syllabus.courseInfo.title)
1189
+ const courseSlug = syllabus.courseInfo.slug
1147
1190
 
1148
1191
  const tutorialDir = `courses/${courseSlug}`
1149
1192
  const learnJson = createLearnJson(syllabus.courseInfo)
@@ -1241,7 +1284,7 @@ export default class ServeCommand extends SessionCommand {
1241
1284
 
1242
1285
  return res.json({
1243
1286
  message: "Course created",
1244
- slug: slugify(syllabus.courseInfo.title),
1287
+ slug: syllabus.courseInfo.slug,
1245
1288
  })
1246
1289
  })
1247
1290
 
@@ -1409,11 +1452,6 @@ export default class ServeCommand extends SessionCommand {
1409
1452
  selectedLang = availableLangs[0]
1410
1453
  }
1411
1454
 
1412
- // console.log(availableLangs, "AVAILABLE LANGs")
1413
- // console.log(selectedLang, "SELECTED LANG")
1414
- // console.log(title, "TITLE")
1415
-
1416
- // 5) Inyectar placeholders en index.html
1417
1455
  const idxTpl = fs.readFileSync(path.join(uiSrc, "index.html"), "utf-8")
1418
1456
  const idxHtml = idxTpl
1419
1457
  .replace(/{{title}}/g, title)
@@ -1485,24 +1523,13 @@ export default class ServeCommand extends SessionCommand {
1485
1523
  }
1486
1524
  )
1487
1525
 
1488
- const indexReadme = await bucket.file(
1489
- `courses/${slug}/README.${
1490
- selectedLang === "us" || selectedLang === "en" ?
1491
- "md" :
1492
- `${selectedLang}.md`
1493
- }`
1494
- )
1495
- const [indexReadmeContent] = await indexReadme.download()
1496
- const indexReadmeString = indexReadmeContent.toString()
1497
- const b64IndexReadme =
1498
- Buffer.from(indexReadmeString).toString("base64")
1499
-
1500
- await handleAssetCreation(
1501
- { token: bcToken, rigobotToken: rigoToken },
1526
+ await createMultiLangAsset(
1527
+ bucket,
1528
+ rigoToken,
1529
+ bcToken,
1530
+ slug,
1502
1531
  fullConfig.config,
1503
- selectedLang,
1504
- rigoRes.data.url,
1505
- b64IndexReadme
1532
+ rigoRes.data.url
1506
1533
  )
1507
1534
 
1508
1535
  rimraf.sync(tmpRoot)
@@ -26,6 +26,7 @@ import ResumeCourseModal from "./components/ResumeCourseModal"
26
26
  import { possiblePurposes, PurposeSelector } from "./components/PurposeSelector"
27
27
  import { useTranslation } from "react-i18next"
28
28
  import NotificationListener from "./components/NotificationListener"
29
+ import { slugify } from "./utils/creatorUtils"
29
30
 
30
31
  function App() {
31
32
  const navigate = useNavigate()
@@ -410,7 +411,8 @@ function App() {
410
411
  lessons,
411
412
  courseInfo: {
412
413
  ...formState,
413
- title: fixTitleLength(res.parsed.title),
414
+ title: res.parsed.title,
415
+ slug: slugify(fixTitleLength(res.parsed.title)),
414
416
  description: res.parsed.description,
415
417
  language:
416
418
  res.parsed.languageCode || formState.language || "en",