@learnpack/learnpack 5.0.331 → 5.0.333

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,23 +20,56 @@ 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);
60
+ const technologies = Array.isArray(learnJson === null || learnJson === void 0 ? void 0 : learnJson.technologies) ?
61
+ learnJson.technologies :
62
+ [];
30
63
  if (!exists) {
31
64
  console_1.default.info("Asset does not exist in this academy, creating it");
32
65
  const asset = await api_1.default.createAsset(sessionPayload.token, {
33
66
  slug: slug,
34
- title: learnJson.title[selectedLang],
67
+ title: assetTitle,
35
68
  lang: selectedLang,
36
69
  graded: true,
37
- description: learnJson.description[selectedLang],
70
+ description: assetDescription,
38
71
  learnpack_deploy_url: learnpackDeployUrl,
39
- technologies: learnJson.technologies.map((tech) => tech.toLowerCase().replace(/\s+/g, "-")),
72
+ technologies: technologies.map((tech) => String(tech).toLowerCase().replace(/\s+/g, "-")),
40
73
  url: learnpackDeployUrl,
41
74
  category: category,
42
75
  owner: user.id,
@@ -60,9 +93,9 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
60
93
  const asset = await api_1.default.updateAsset(sessionPayload.token, slug, {
61
94
  graded: true,
62
95
  learnpack_deploy_url: learnpackDeployUrl,
63
- title: learnJson.title[selectedLang],
96
+ title: assetTitle,
64
97
  category: category,
65
- description: learnJson.description[selectedLang],
98
+ description: assetDescription,
66
99
  all_translations,
67
100
  });
68
101
  try {
@@ -82,6 +115,35 @@ const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, lear
82
115
  }
83
116
  };
84
117
  exports.handleAssetCreation = handleAssetCreation;
118
+ const createMultiLangAssetFromDisk = async (sessionPayload, learnJson, deployUrl) => {
119
+ const availableLangs = getAvailableLangs(learnJson);
120
+ if (availableLangs.length === 0) {
121
+ console_1.default.error("No languages found in learn.json.title. Add at least one language (e.g. title.en).");
122
+ return;
123
+ }
124
+ const all_translations = [];
125
+ for (const lang of availableLangs) {
126
+ const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
127
+ let indexReadmeString = "";
128
+ try {
129
+ if (fs.existsSync(readmePath)) {
130
+ indexReadmeString = fs.readFileSync(readmePath, "utf-8");
131
+ }
132
+ }
133
+ catch (error) {
134
+ console_1.default.error("Error reading index readme:", error);
135
+ indexReadmeString = "";
136
+ }
137
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
138
+ // eslint-disable-next-line no-await-in-loop
139
+ const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, all_translations);
140
+ if (!asset) {
141
+ console_1.default.debug("Could not create/update asset for lang", lang);
142
+ continue;
143
+ }
144
+ all_translations.push(asset.slug);
145
+ }
146
+ };
85
147
  const runAudit = (strict) => {
86
148
  try {
87
149
  console_1.default.info("Running learnpack audit before publishing...");
@@ -245,8 +307,11 @@ class BuildCommand extends SessionCommand_1.default {
245
307
  const buildManifestPWA = path.join(buildDir, "manifest.webmanifest");
246
308
  if (fs.existsSync(indexHtmlPath)) {
247
309
  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";
310
+ const selectedLang = getDefaultLang(learnJson);
311
+ const description = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang) ||
312
+ "LearnPack is awesome!";
313
+ const title = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
314
+ "LearnPack: Interactive Learning as a Service";
250
315
  const previewUrl = learnJson.preview ||
251
316
  "https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg";
252
317
  // Replace placeholders and the <title>Old title </title> tag for a new tag with the title
@@ -265,7 +330,12 @@ class BuildCommand extends SessionCommand_1.default {
265
330
  }
266
331
  if (fs.existsSync(manifestPWA)) {
267
332
  let manifestPWAContent = fs.readFileSync(manifestPWA, "utf-8");
268
- manifestPWAContent = manifestPWAContent.replace("{{course_title}}", learnJson.title.us);
333
+ const selectedLang = getDefaultLang(learnJson);
334
+ const courseTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
335
+ getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "us") ||
336
+ getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "en") ||
337
+ "LearnPack";
338
+ manifestPWAContent = manifestPWAContent.replace("{{course_title}}", courseTitle);
269
339
  const courseShortName = { answer: "testing-tutorial" };
270
340
  // const courseShortName = await generateCourseShortName(rigoToken, {
271
341
  // learnJSON: JSON.stringify(learnJson),
@@ -314,7 +384,7 @@ class BuildCommand extends SessionCommand_1.default {
314
384
  console.log(res.data);
315
385
  fs.unlinkSync(zipFilePath);
316
386
  this.removeDirectory(buildDir);
317
- await (0, exports.handleAssetCreation)(sessionPayload, learnJson, "en", res.data.url, "", []);
387
+ await createMultiLangAssetFromDisk({ token: sessionPayload.token, rigobotToken: rigoToken }, learnJson, res.data.url);
318
388
  }
319
389
  catch (error) {
320
390
  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.333",
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,22 +67,38 @@ 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)
43
86
 
87
+ const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
88
+ learnJson.technologies :
89
+ []
90
+
44
91
  if (!exists) {
45
92
  Console.info("Asset does not exist in this academy, creating it")
46
93
  const asset = await api.createAsset(sessionPayload.token, {
47
94
  slug: slug,
48
- title: learnJson.title[selectedLang],
95
+ title: assetTitle,
49
96
  lang: selectedLang,
50
97
  graded: true,
51
- description: learnJson.description[selectedLang],
98
+ description: assetDescription,
52
99
  learnpack_deploy_url: learnpackDeployUrl,
53
- technologies: learnJson.technologies.map((tech: string) =>
54
- tech.toLowerCase().replace(/\s+/g, "-")
100
+ technologies: technologies.map((tech: unknown) =>
101
+ String(tech).toLowerCase().replace(/\s+/g, "-")
55
102
  ),
56
103
  url: learnpackDeployUrl,
57
104
  category: category,
@@ -81,9 +128,9 @@ export const handleAssetCreation = async (
81
128
  const asset = await api.updateAsset(sessionPayload.token, slug, {
82
129
  graded: true,
83
130
  learnpack_deploy_url: learnpackDeployUrl,
84
- title: learnJson.title[selectedLang],
131
+ title: assetTitle,
85
132
  category: category,
86
- description: learnJson.description[selectedLang],
133
+ description: assetDescription,
87
134
  all_translations,
88
135
  })
89
136
  try {
@@ -106,6 +153,58 @@ export const handleAssetCreation = async (
106
153
  }
107
154
  }
108
155
 
156
+ const createMultiLangAssetFromDisk = async (
157
+ sessionPayload: { token: string; rigobotToken: string },
158
+ learnJson: any,
159
+ deployUrl: string
160
+ ) => {
161
+ const availableLangs = getAvailableLangs(learnJson)
162
+
163
+ if (availableLangs.length === 0) {
164
+ Console.error(
165
+ "No languages found in learn.json.title. Add at least one language (e.g. title.en)."
166
+ )
167
+ return
168
+ }
169
+
170
+ const all_translations: string[] = []
171
+ for (const lang of availableLangs) {
172
+ const readmePath = path.join(
173
+ process.cwd(),
174
+ `README${getReadmeExtension(lang)}`
175
+ )
176
+
177
+ let indexReadmeString = ""
178
+ try {
179
+ if (fs.existsSync(readmePath)) {
180
+ indexReadmeString = fs.readFileSync(readmePath, "utf-8")
181
+ }
182
+ } catch (error) {
183
+ Console.error("Error reading index readme:", error)
184
+ indexReadmeString = ""
185
+ }
186
+
187
+ const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64")
188
+
189
+ // eslint-disable-next-line no-await-in-loop
190
+ const asset = await handleAssetCreation(
191
+ sessionPayload,
192
+ learnJson,
193
+ lang,
194
+ deployUrl,
195
+ b64IndexReadme,
196
+ all_translations
197
+ )
198
+
199
+ if (!asset) {
200
+ Console.debug("Could not create/update asset for lang", lang)
201
+ continue
202
+ }
203
+
204
+ all_translations.push(asset.slug)
205
+ }
206
+ }
207
+
109
208
  const runAudit = (strict: boolean) => {
110
209
  try {
111
210
  Console.info("Running learnpack audit before publishing...")
@@ -362,9 +461,13 @@ class BuildCommand extends SessionCommand {
362
461
  if (fs.existsSync(indexHtmlPath)) {
363
462
  let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8")
364
463
 
365
- const description = learnJson.description.en || "LearnPack is awesome!"
464
+ const selectedLang = getDefaultLang(learnJson)
465
+ const description =
466
+ getLocalizedValue(learnJson?.description, selectedLang) ||
467
+ "LearnPack is awesome!"
366
468
  const title =
367
- learnJson.title.en || "LearnPack: Interactive Learning as a Service"
469
+ getLocalizedValue(learnJson?.title, selectedLang) ||
470
+ "LearnPack: Interactive Learning as a Service"
368
471
 
369
472
  const previewUrl =
370
473
  learnJson.preview ||
@@ -386,9 +489,15 @@ class BuildCommand extends SessionCommand {
386
489
 
387
490
  if (fs.existsSync(manifestPWA)) {
388
491
  let manifestPWAContent = fs.readFileSync(manifestPWA, "utf-8")
492
+ const selectedLang = getDefaultLang(learnJson)
493
+ const courseTitle =
494
+ getLocalizedValue(learnJson?.title, selectedLang) ||
495
+ getLocalizedValue(learnJson?.title, "us") ||
496
+ getLocalizedValue(learnJson?.title, "en") ||
497
+ "LearnPack"
389
498
  manifestPWAContent = manifestPWAContent.replace(
390
499
  "{{course_title}}",
391
- learnJson.title.us
500
+ courseTitle
392
501
  )
393
502
 
394
503
  const courseShortName = { answer: "testing-tutorial" }
@@ -454,13 +563,10 @@ class BuildCommand extends SessionCommand {
454
563
  fs.unlinkSync(zipFilePath)
455
564
  this.removeDirectory(buildDir)
456
565
 
457
- await handleAssetCreation(
458
- sessionPayload,
566
+ await createMultiLangAssetFromDisk(
567
+ { token: sessionPayload.token, rigobotToken: rigoToken },
459
568
  learnJson,
460
- "en",
461
- res.data.url,
462
- "",
463
- []
569
+ res.data.url
464
570
  )
465
571
  } catch (error) {
466
572
  if (axios.isAxiosError(error)) {