@learnpack/learnpack 5.0.331 → 5.0.332

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.
@@ -20,21 +20,51 @@ const rigoActions_1 = require("../utils/rigoActions");
20
20
  const misc_1 = require("../utils/misc");
21
21
  const creatorUtilities_1 = require("../utils/creatorUtilities");
22
22
  const uploadZipEndpont = api_1.RIGOBOT_HOST + "/v1/learnpack/upload";
23
+ const getAvailableLangs = (learnJson) => {
24
+ const langs = Object.keys((learnJson === null || learnJson === void 0 ? void 0 : learnJson.title) || {});
25
+ return langs.filter((l) => typeof l === "string" && l.length > 0);
26
+ };
27
+ const getDefaultLang = (learnJson) => {
28
+ const availableLangs = getAvailableLangs(learnJson);
29
+ if (availableLangs.length === 0)
30
+ return "en";
31
+ return availableLangs.includes("en") ? "en" : availableLangs[0];
32
+ };
33
+ const getLocalizedValue = (translations, lang, fallbackLangs = ["en", "us"]) => {
34
+ if (!translations || typeof translations !== "object")
35
+ return "";
36
+ const direct = translations[lang];
37
+ if (typeof direct === "string" && direct.trim().length > 0)
38
+ return direct;
39
+ for (const fb of fallbackLangs) {
40
+ const v = translations[fb];
41
+ if (typeof v === "string" && v.trim().length > 0)
42
+ return v;
43
+ }
44
+ const firstKey = Object.keys(translations)[0];
45
+ const first = firstKey ? translations[firstKey] : "";
46
+ return typeof first === "string" ? first : "";
47
+ };
23
48
  const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, all_translations = []) => {
24
49
  const category = "uncategorized";
25
50
  try {
26
51
  const user = await api_1.default.validateToken(sessionPayload.token);
27
- let slug = (0, creatorUtilities_1.slugify)(learnJson.title[selectedLang]).slice(0, 47);
52
+ const assetTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang);
53
+ const assetDescription = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang);
54
+ if (!assetTitle) {
55
+ throw new Error(`Missing learn.json title for language "${selectedLang}"`);
56
+ }
57
+ let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
28
58
  slug = `${slug}-${selectedLang}`;
29
59
  const { exists } = await api_1.default.doesAssetExists(sessionPayload.token, slug);
30
60
  if (!exists) {
31
61
  console_1.default.info("Asset does not exist in this academy, creating it");
32
62
  const asset = await api_1.default.createAsset(sessionPayload.token, {
33
63
  slug: slug,
34
- title: learnJson.title[selectedLang],
64
+ title: assetTitle,
35
65
  lang: selectedLang,
36
66
  graded: true,
37
- description: learnJson.description[selectedLang],
67
+ description: assetDescription,
38
68
  learnpack_deploy_url: learnpackDeployUrl,
39
69
  technologies: learnJson.technologies.map((tech) => tech.toLowerCase().replace(/\s+/g, "-")),
40
70
  url: learnpackDeployUrl,
@@ -60,9 +90,9 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
60
90
  const asset = await api_1.default.updateAsset(sessionPayload.token, slug, {
61
91
  graded: true,
62
92
  learnpack_deploy_url: learnpackDeployUrl,
63
- title: learnJson.title[selectedLang],
93
+ title: assetTitle,
64
94
  category: category,
65
- description: learnJson.description[selectedLang],
95
+ description: assetDescription,
66
96
  all_translations,
67
97
  });
68
98
  try {
@@ -82,6 +112,35 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
82
112
  }
83
113
  };
84
114
  exports.handleAssetCreation = handleAssetCreation;
115
+ const createMultiLangAssetFromDisk = async (sessionPayload, learnJson, deployUrl) => {
116
+ const availableLangs = getAvailableLangs(learnJson);
117
+ if (availableLangs.length === 0) {
118
+ console_1.default.error("No languages found in learn.json.title. Add at least one language (e.g. title.en).");
119
+ return;
120
+ }
121
+ const all_translations = [];
122
+ for (const lang of availableLangs) {
123
+ const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
124
+ let indexReadmeString = "";
125
+ try {
126
+ if (fs.existsSync(readmePath)) {
127
+ indexReadmeString = fs.readFileSync(readmePath, "utf-8");
128
+ }
129
+ }
130
+ catch (error) {
131
+ console_1.default.error("Error reading index readme:", error);
132
+ indexReadmeString = "";
133
+ }
134
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
135
+ // eslint-disable-next-line no-await-in-loop
136
+ const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, all_translations);
137
+ if (!asset) {
138
+ console_1.default.debug("Could not create/update asset for lang", lang);
139
+ continue;
140
+ }
141
+ all_translations.push(asset.slug);
142
+ }
143
+ };
85
144
  const runAudit = (strict) => {
86
145
  try {
87
146
  console_1.default.info("Running learnpack audit before publishing...");
@@ -245,8 +304,11 @@ class BuildCommand extends SessionCommand_1.default {
245
304
  const buildManifestPWA = path.join(buildDir, "manifest.webmanifest");
246
305
  if (fs.existsSync(indexHtmlPath)) {
247
306
  let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8");
248
- const description = learnJson.description.en || "LearnPack is awesome!";
249
- const title = learnJson.title.en || "LearnPack: Interactive Learning as a Service";
307
+ const selectedLang = getDefaultLang(learnJson);
308
+ const description = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang) ||
309
+ "LearnPack is awesome!";
310
+ const title = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
311
+ "LearnPack: Interactive Learning as a Service";
250
312
  const previewUrl = learnJson.preview ||
251
313
  "https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg";
252
314
  // Replace placeholders and the <title>Old title </title> tag for a new tag with the title
@@ -265,7 +327,12 @@ class BuildCommand extends SessionCommand_1.default {
265
327
  }
266
328
  if (fs.existsSync(manifestPWA)) {
267
329
  let manifestPWAContent = fs.readFileSync(manifestPWA, "utf-8");
268
- manifestPWAContent = manifestPWAContent.replace("{{course_title}}", learnJson.title.us);
330
+ const selectedLang = getDefaultLang(learnJson);
331
+ const courseTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
332
+ getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "us") ||
333
+ getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "en") ||
334
+ "LearnPack";
335
+ manifestPWAContent = manifestPWAContent.replace("{{course_title}}", courseTitle);
269
336
  const courseShortName = { answer: "testing-tutorial" };
270
337
  // const courseShortName = await generateCourseShortName(rigoToken, {
271
338
  // learnJSON: JSON.stringify(learnJson),
@@ -314,7 +381,7 @@ class BuildCommand extends SessionCommand_1.default {
314
381
  console.log(res.data);
315
382
  fs.unlinkSync(zipFilePath);
316
383
  this.removeDirectory(buildDir);
317
- await (0, exports.handleAssetCreation)(sessionPayload, learnJson, "en", res.data.url, "", []);
384
+ await createMultiLangAssetFromDisk({ token: sessionPayload.token, rigobotToken: rigoToken }, learnJson, res.data.url);
318
385
  }
319
386
  catch (error) {
320
387
  if (axios_1.default.isAxiosError(error)) {
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.331",
4
+ "version": "5.0.332",
5
5
  "author": "Alejandro Sanchez @alesanchezr",
6
6
  "contributors": [
7
7
  {
@@ -19,10 +19,41 @@ 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
+ import { getReadmeExtension, slugify } from "../utils/creatorUtilities"
23
23
 
24
24
  const uploadZipEndpont = RIGOBOT_HOST + "/v1/learnpack/upload"
25
25
 
26
+ const getAvailableLangs = (learnJson: any): string[] => {
27
+ const langs = Object.keys(learnJson?.title || {})
28
+ return langs.filter((l) => typeof l === "string" && l.length > 0)
29
+ }
30
+
31
+ const getDefaultLang = (learnJson: any): string => {
32
+ const availableLangs = getAvailableLangs(learnJson)
33
+ if (availableLangs.length === 0) return "en"
34
+ return availableLangs.includes("en") ? "en" : availableLangs[0]
35
+ }
36
+
37
+ const getLocalizedValue = (
38
+ translations: Record<string, any> | undefined,
39
+ lang: string,
40
+ fallbackLangs: string[] = ["en", "us"]
41
+ ): string => {
42
+ if (!translations || typeof translations !== "object") return ""
43
+
44
+ const direct = translations[lang]
45
+ if (typeof direct === "string" && direct.trim().length > 0) return direct
46
+
47
+ for (const fb of fallbackLangs) {
48
+ const v = translations[fb]
49
+ if (typeof v === "string" && v.trim().length > 0) return v
50
+ }
51
+
52
+ const firstKey = Object.keys(translations)[0]
53
+ const first = firstKey ? translations[firstKey] : ""
54
+ return typeof first === "string" ? first : ""
55
+ }
56
+
26
57
  export const handleAssetCreation = async (
27
58
  sessionPayload: { token: string; rigobotToken: string },
28
59
  learnJson: any,
@@ -36,7 +67,19 @@ export const handleAssetCreation = async (
36
67
  try {
37
68
  const user = await api.validateToken(sessionPayload.token)
38
69
 
39
- let slug = slugify(learnJson.title[selectedLang]).slice(0, 47)
70
+ const assetTitle = getLocalizedValue(learnJson?.title, selectedLang)
71
+ const assetDescription = getLocalizedValue(
72
+ learnJson?.description,
73
+ selectedLang
74
+ )
75
+
76
+ if (!assetTitle) {
77
+ throw new Error(
78
+ `Missing learn.json title for language "${selectedLang}"`
79
+ )
80
+ }
81
+
82
+ let slug = slugify(assetTitle).slice(0, 47)
40
83
  slug = `${slug}-${selectedLang}`
41
84
 
42
85
  const { exists } = await api.doesAssetExists(sessionPayload.token, slug)
@@ -45,10 +88,10 @@ export const handleAssetCreation = async (
45
88
  Console.info("Asset does not exist in this academy, creating it")
46
89
  const asset = await api.createAsset(sessionPayload.token, {
47
90
  slug: slug,
48
- title: learnJson.title[selectedLang],
91
+ title: assetTitle,
49
92
  lang: selectedLang,
50
93
  graded: true,
51
- description: learnJson.description[selectedLang],
94
+ description: assetDescription,
52
95
  learnpack_deploy_url: learnpackDeployUrl,
53
96
  technologies: learnJson.technologies.map((tech: string) =>
54
97
  tech.toLowerCase().replace(/\s+/g, "-")
@@ -81,9 +124,9 @@ export const handleAssetCreation = async (
81
124
  const asset = await api.updateAsset(sessionPayload.token, slug, {
82
125
  graded: true,
83
126
  learnpack_deploy_url: learnpackDeployUrl,
84
- title: learnJson.title[selectedLang],
127
+ title: assetTitle,
85
128
  category: category,
86
- description: learnJson.description[selectedLang],
129
+ description: assetDescription,
87
130
  all_translations,
88
131
  })
89
132
  try {
@@ -106,6 +149,58 @@ export const handleAssetCreation = async (
106
149
  }
107
150
  }
108
151
 
152
+ const createMultiLangAssetFromDisk = async (
153
+ sessionPayload: { token: string; rigobotToken: string },
154
+ learnJson: any,
155
+ deployUrl: string
156
+ ) => {
157
+ const availableLangs = getAvailableLangs(learnJson)
158
+
159
+ if (availableLangs.length === 0) {
160
+ Console.error(
161
+ "No languages found in learn.json.title. Add at least one language (e.g. title.en)."
162
+ )
163
+ return
164
+ }
165
+
166
+ const all_translations: string[] = []
167
+ for (const lang of availableLangs) {
168
+ const readmePath = path.join(
169
+ process.cwd(),
170
+ `README${getReadmeExtension(lang)}`
171
+ )
172
+
173
+ let indexReadmeString = ""
174
+ try {
175
+ if (fs.existsSync(readmePath)) {
176
+ indexReadmeString = fs.readFileSync(readmePath, "utf-8")
177
+ }
178
+ } catch (error) {
179
+ Console.error("Error reading index readme:", error)
180
+ indexReadmeString = ""
181
+ }
182
+
183
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
184
+
185
+ // eslint-disable-next-line no-await-in-loop
186
+ const asset = await handleAssetCreation(
187
+ sessionPayload,
188
+ learnJson,
189
+ lang,
190
+ deployUrl,
191
+ b64IndexReadme,
192
+ all_translations
193
+ )
194
+
195
+ if (!asset) {
196
+ Console.debug("Could not create/update asset for lang", lang)
197
+ continue
198
+ }
199
+
200
+ all_translations.push(asset.slug)
201
+ }
202
+ }
203
+
109
204
  const runAudit = (strict: boolean) => {
110
205
  try {
111
206
  Console.info("Running learnpack audit before publishing...")
@@ -362,9 +457,13 @@ class BuildCommand extends SessionCommand {
362
457
  if (fs.existsSync(indexHtmlPath)) {
363
458
  let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
364
459
 
365
- const description = learnJson.description.en || "LearnPack is awesome!"
460
+ const selectedLang = getDefaultLang(learnJson)
461
+ const description =
462
+ getLocalizedValue(learnJson?.description, selectedLang) ||
463
+ "LearnPack is awesome!"
366
464
  const title =
367
- learnJson.title.en || "LearnPack: Interactive Learning as a Service"
465
+ getLocalizedValue(learnJson?.title, selectedLang) ||
466
+ "LearnPack: Interactive Learning as a Service"
368
467
 
369
468
  const previewUrl =
370
469
  learnJson.preview ||
@@ -386,9 +485,15 @@ class BuildCommand extends SessionCommand {
386
485
 
387
486
  if (fs.existsSync(manifestPWA)) {
388
487
  let manifestPWAContent = fs.readFileSync(manifestPWA, "utf-8")
488
+ const selectedLang = getDefaultLang(learnJson)
489
+ const courseTitle =
490
+ getLocalizedValue(learnJson?.title, selectedLang) ||
491
+ getLocalizedValue(learnJson?.title, "us") ||
492
+ getLocalizedValue(learnJson?.title, "en") ||
493
+ "LearnPack"
389
494
  manifestPWAContent = manifestPWAContent.replace(
390
495
  "{{course_title}}",
391
- learnJson.title.us
496
+ courseTitle
392
497
  )
393
498
 
394
499
  const courseShortName = { answer: "testing-tutorial" }
@@ -454,13 +559,10 @@ class BuildCommand extends SessionCommand {
454
559
  fs.unlinkSync(zipFilePath)
455
560
  this.removeDirectory(buildDir)
456
561
 
457
- await handleAssetCreation(
458
- sessionPayload,
562
+ await createMultiLangAssetFromDisk(
563
+ { token: sessionPayload.token, rigobotToken: rigoToken },
459
564
  learnJson,
460
- "en",
461
- res.data.url,
462
- "",
463
- []
565
+ res.data.url
464
566
  )
465
567
  } catch (error) {
466
568
  if (axios.isAxiosError(error)) {