@learnpack/learnpack 5.0.357 → 5.0.359

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.
@@ -1,14 +1,4 @@
1
1
  import SessionCommand from "../utils/SessionCommand";
2
- type ExistingAssetInfo = {
3
- lang: string;
4
- slug: string;
5
- exists: boolean;
6
- academyId?: number;
7
- };
8
- export declare const handleAssetCreation: (sessionPayload: {
9
- token: string;
10
- rigobotToken: string;
11
- }, learnJson: any, selectedLang: string, learnpackDeployUrl: string, b64IndexReadme: string, academyId: number | undefined, learnpackId: number, preflightInfo?: ExistingAssetInfo, all_translations?: string[]) => Promise<any>;
12
2
  declare class BuildCommand extends SessionCommand {
13
3
  static description: string;
14
4
  static flags: {
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleAssetCreation = void 0;
4
3
  /* eslint-disable arrow-parens */
5
4
  /* eslint-disable unicorn/no-array-for-each */
6
5
  const child_process_1 = require("child_process");
@@ -19,181 +18,56 @@ const prompts = require("prompts");
19
18
  const rigoActions_1 = require("../utils/rigoActions");
20
19
  const misc_1 = require("../utils/misc");
21
20
  const creatorUtilities_1 = require("../utils/creatorUtilities");
21
+ const branding_1 = require("../utils/branding");
22
+ const assetResolver_1 = require("../utils/assetResolver");
23
+ const assetSync_1 = require("../utils/assetSync");
22
24
  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
25
  const getDefaultLang = (learnJson) => {
28
- const availableLangs = getAvailableLangs(learnJson);
26
+ const availableLangs = (0, assetResolver_1.getAvailableLangs)(learnJson);
29
27
  if (availableLangs.length === 0)
30
28
  return "en";
31
29
  return availableLangs.includes("en") ? "en" : availableLangs[0];
32
30
  };
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
- };
48
- const getExistingAssetsInfo = async (token, learnJson) => {
49
- const availableLangs = getAvailableLangs(learnJson);
50
- const results = [];
51
- for (const lang of availableLangs) {
52
- const assetTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, lang);
53
- if (!assetTitle)
54
- continue;
55
- let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
56
- slug = `${slug}-${lang}`;
57
- // eslint-disable-next-line no-await-in-loop
58
- const { exists, academyId } = await api_1.default.doesAssetExists(token, slug);
59
- results.push({ lang, slug, exists, academyId });
60
- }
61
- return results;
62
- };
63
- const determinePublishAcademyMode = (existingAssets) => {
64
- const academyIds = existingAssets
65
- .filter((a) => a.exists && a.academyId !== undefined)
66
- .map((a) => a.academyId);
67
- const unique = [...new Set(academyIds)];
68
- if (unique.length === 0)
69
- return { type: "select" };
70
- if (unique.length === 1)
71
- return { type: "locked", academyId: unique[0] };
72
- return { type: "conflict", academies: unique };
73
- };
74
- const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, academyId, learnpackId, preflightInfo, all_translations = []) => {
75
- const category = "uncategorized";
31
+ const readReadmeFromDisk = async (lang) => {
32
+ const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
76
33
  try {
77
- const user = await api_1.default.validateToken(sessionPayload.token);
78
- const assetTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang);
79
- const assetDescription = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang);
80
- if (!assetTitle) {
81
- throw new Error(`Missing learn.json title for language "${selectedLang}"`);
82
- }
83
- let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
84
- slug = `${slug}-${selectedLang}`;
85
- // Use pre-flight info when available to avoid an extra GET request
86
- const { exists, academyId: existingAcademyId } = preflightInfo !== null && preflightInfo !== void 0 ? preflightInfo : (await api_1.default.doesAssetExists(sessionPayload.token, slug));
87
- // const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
88
- // learnJson.technologies :
89
- // []
90
- if (!exists) {
91
- console_1.default.info("Asset does not exist in this academy, creating it");
92
- const assetPayload = {
93
- slug: slug,
94
- title: assetTitle,
95
- lang: selectedLang,
96
- graded: true,
97
- description: assetDescription,
98
- learnpack_deploy_url: learnpackDeployUrl,
99
- // technologies: technologies.map((tech: unknown) =>
100
- // String(tech).toLowerCase().replace(/\s+/g, "-")
101
- // ),
102
- technologies: [],
103
- url: learnpackDeployUrl,
104
- category: category,
105
- owner: user.id,
106
- author: user.id,
107
- preview: learnJson.preview,
108
- readme_raw: b64IndexReadme,
109
- all_translations,
110
- learnpack_id: learnpackId,
111
- };
112
- if (academyId !== undefined) {
113
- assetPayload.academy_id = academyId;
114
- }
115
- const asset = await api_1.default.createAsset(sessionPayload.token, assetPayload);
116
- try {
117
- await api_1.default.updateRigoPackage(sessionPayload.rigobotToken.trim(), learnJson.slug, {
118
- asset_id: asset.id,
119
- });
120
- }
121
- catch (error) {
122
- console_1.default.error("Error updating Rigo package:", error);
123
- }
124
- console_1.default.info("Asset created with id", asset.id);
125
- return asset;
126
- }
127
- console_1.default.info("Asset exists, updating it");
128
- const updatePayload = {
129
- graded: true,
130
- learnpack_deploy_url: learnpackDeployUrl,
131
- title: assetTitle,
132
- category: category,
133
- description: assetDescription,
134
- all_translations,
135
- learnpack_id: learnpackId,
136
- };
137
- // Only set academy when the asset has none yet and the user selected one
138
- if (existingAcademyId === undefined && academyId !== undefined) {
139
- updatePayload.academy_id = academyId;
140
- }
141
- const asset = await api_1.default.updateAsset(sessionPayload.token, slug, updatePayload);
142
- try {
143
- await api_1.default.updateRigoPackage(sessionPayload.rigobotToken.trim(), learnJson.slug, {
144
- asset_id: asset.id,
145
- });
146
- }
147
- catch (error) {
148
- console_1.default.error("Error updating Rigo package:", error);
149
- }
150
- console_1.default.info("Asset updated with id", asset.id);
151
- return asset;
34
+ return fs.existsSync(readmePath) ?
35
+ fs.readFileSync(readmePath, "utf-8") :
36
+ "";
152
37
  }
153
38
  catch (error) {
154
- console_1.default.error("Error updating or creating asset:", error);
155
- throw error;
39
+ console_1.default.error("Error reading index readme:", error);
40
+ return "";
156
41
  }
157
42
  };
158
- exports.handleAssetCreation = handleAssetCreation;
159
- const createMultiLangAssetFromDisk = async (sessionPayload, learnJson, deployUrl, selectedAcademyId, existingAssetsInfo = []) => {
160
- const availableLangs = getAvailableLangs(learnJson);
161
- if (availableLangs.length === 0) {
43
+ const syncAssetsFromDisk = async (sessionPayload, learnJson, deployUrl, selectedAcademyId, resolution) => {
44
+ if ((0, assetResolver_1.getAvailableLangs)(learnJson).length === 0) {
162
45
  console_1.default.error("No languages found in learn.json.title. Add at least one language (e.g. title.en).");
163
46
  return;
164
47
  }
165
- const learnpackId = await api_1.default.resolveLearnpackPackageId(sessionPayload.rigobotToken, learnJson.slug);
166
- if (learnpackId === null) {
167
- console_1.default.warning("Breathecode assets skipped: could not resolve Learnpack package id");
168
- return;
48
+ // The pre-upload resolution is reused unless the package did not exist in
49
+ // Rigobot yet (first publish): the upload has just created it.
50
+ const result = await (0, assetSync_1.syncPackageAssets)({
51
+ bcToken: sessionPayload.token,
52
+ rigoToken: sessionPayload.rigobotToken,
53
+ packageSlug: learnJson.slug,
54
+ learnJson,
55
+ deployUrl,
56
+ academyId: selectedAcademyId,
57
+ readReadme: readReadmeFromDisk,
58
+ resolution: resolution.learnpackId === null ? undefined : resolution,
59
+ });
60
+ for (const warning of result.warnings)
61
+ console_1.default.warning(warning);
62
+ for (const synced of result.synced) {
63
+ console_1.default.info(`Asset ${synced.action} for "${synced.lang}": #${synced.asset.id} ${synced.asset.slug}`);
169
64
  }
170
- const all_translations = [];
171
- for (const lang of availableLangs) {
172
- const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
173
- let indexReadmeString = "";
174
- try {
175
- if (fs.existsSync(readmePath)) {
176
- indexReadmeString = fs.readFileSync(readmePath, "utf-8");
177
- }
65
+ for (const error of result.errors) {
66
+ if (error.kind === "lang_error") {
67
+ console_1.default.error(`Asset for "${error.lang}": ${error.error.detail}`);
178
68
  }
179
- catch (error) {
180
- console_1.default.error("Error reading index readme:", error);
181
- indexReadmeString = "";
182
- }
183
- const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
184
- const preflightInfo = existingAssetsInfo.find((a) => a.lang === lang);
185
- try {
186
- // eslint-disable-next-line no-await-in-loop
187
- const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, selectedAcademyId, learnpackId, preflightInfo, all_translations);
188
- if (!asset) {
189
- console_1.default.debug("Could not create/update asset for lang", lang);
190
- continue;
191
- }
192
- all_translations.push(asset.slug);
193
- }
194
- catch (error) {
195
- console_1.default.error("Error creating asset for language", lang, error);
196
- // Continue with other languages
69
+ else {
70
+ console_1.default.error(error.error.detail);
197
71
  }
198
72
  }
199
73
  };
@@ -312,8 +186,19 @@ class BuildCommand extends SessionCommand_1.default {
312
186
  process.exit(1);
313
187
  }
314
188
  console_1.default.info("Checking existing assets...");
315
- const existingAssetsInfo = await getExistingAssetsInfo(sessionPayload.token, learnJson);
316
- const academyMode = determinePublishAcademyMode(existingAssetsInfo);
189
+ const resolution = await (0, assetResolver_1.resolvePackageAssets)({
190
+ bcToken: sessionPayload.token,
191
+ rigoToken: rigoToken,
192
+ packageSlug: learnJson.slug,
193
+ learnJson,
194
+ });
195
+ for (const warning of resolution.warnings)
196
+ console_1.default.warning(warning);
197
+ if (resolution.packageState === "unknown") {
198
+ console_1.default.error(`${resolution.packageError}. Existing assets cannot be checked, so publishing now could create duplicates. Try again later.`);
199
+ process.exit(1);
200
+ }
201
+ const academyMode = (0, assetResolver_1.determineAcademyMode)(resolution);
317
202
  let selectedAcademyId;
318
203
  if (academyMode.type === "conflict") {
319
204
  console_1.default.warning(`Some of your assets are associated with different academies ` +
@@ -379,12 +264,11 @@ class BuildCommand extends SessionCommand_1.default {
379
264
  if (fs.existsSync(indexHtmlPath)) {
380
265
  let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8");
381
266
  const selectedLang = getDefaultLang(learnJson);
382
- const description = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang) ||
267
+ const description = (0, assetSync_1.getLocalizedValue)(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang) ||
383
268
  "LearnPack is awesome!";
384
- const title = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
269
+ const title = (0, assetSync_1.getLocalizedValue)(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
385
270
  "LearnPack: Interactive Learning as a Service";
386
- const previewUrl = learnJson.preview ||
387
- "https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/public/learnpack.svg";
271
+ const previewUrl = learnJson.preview || (0, branding_1.getFallbackPreviewUrl)();
388
272
  // Replace placeholders and the <title>Old title </title> tag for a new tag with the title
389
273
  indexHtmlContent = indexHtmlContent
390
274
  .replace(/{{description}}/g, description)
@@ -402,9 +286,9 @@ class BuildCommand extends SessionCommand_1.default {
402
286
  if (fs.existsSync(manifestPWA)) {
403
287
  let manifestPWAContent = fs.readFileSync(manifestPWA, "utf-8");
404
288
  const selectedLang = getDefaultLang(learnJson);
405
- const courseTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
406
- getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "us") ||
407
- getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "en") ||
289
+ const courseTitle = (0, assetSync_1.getLocalizedValue)(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang) ||
290
+ (0, assetSync_1.getLocalizedValue)(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "us") ||
291
+ (0, assetSync_1.getLocalizedValue)(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, "en") ||
408
292
  "LearnPack";
409
293
  manifestPWAContent = manifestPWAContent.replace("{{course_title}}", courseTitle);
410
294
  const courseShortName = { answer: "testing-tutorial" };
@@ -461,7 +345,7 @@ class BuildCommand extends SessionCommand_1.default {
461
345
  console.log(res.data);
462
346
  fs.unlinkSync(zipFilePath);
463
347
  this.removeDirectory(buildDir);
464
- await createMultiLangAssetFromDisk({ token: sessionPayload.token, rigobotToken: rigoToken }, learnJson, res.data.url, selectedAcademyId, existingAssetsInfo);
348
+ await syncAssetsFromDisk({ token: sessionPayload.token, rigobotToken: rigoToken }, learnJson, res.data.url, selectedAcademyId, resolution);
465
349
  }
466
350
  catch (error) {
467
351
  if (axios_1.default.isAxiosError(error)) {