@learnpack/learnpack 5.0.358 → 5.0.360

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.
Files changed (47) hide show
  1. package/lib/commands/publish.d.ts +0 -10
  2. package/lib/commands/publish.js +52 -168
  3. package/lib/commands/serve.js +52 -116
  4. package/lib/creatorDist/assets/{index-zrPponAn.css → index-B7s5Nmq8.css} +59 -0
  5. package/lib/creatorDist/assets/{index-D6pmbMe9.js → index-GYxGB_X0.js} +22609 -22363
  6. package/lib/creatorDist/index.html +2 -2
  7. package/lib/utils/api.d.ts +62 -15
  8. package/lib/utils/api.js +124 -45
  9. package/lib/utils/assetResolver.d.ts +116 -0
  10. package/lib/utils/assetResolver.js +268 -0
  11. package/lib/utils/assetSync.d.ts +48 -0
  12. package/lib/utils/assetSync.js +267 -0
  13. package/lib/utils/creatorUtilities.d.ts +1 -0
  14. package/lib/utils/creatorUtilities.js +17 -1
  15. package/package.json +1 -1
  16. package/src/commands/publish.ts +61 -255
  17. package/src/commands/serve.ts +65 -140
  18. package/src/creator/src/App.test.tsx +338 -0
  19. package/src/creator/src/App.tsx +152 -26
  20. package/src/creator/src/components/CourseLanguagePicker.tsx +51 -0
  21. package/src/creator/src/components/LanguageDetectionModal.tsx +29 -9
  22. package/src/creator/src/components/StepWizard.tsx +11 -3
  23. package/src/creator/src/components/Uploader.test.tsx +65 -0
  24. package/src/creator/src/components/Uploader.tsx +9 -3
  25. package/src/creator/src/locales/en.json +7 -0
  26. package/src/creator/src/locales/es.json +7 -0
  27. package/src/creator/src/utils/courseLanguage.test.ts +215 -0
  28. package/src/creator/src/utils/courseLanguage.ts +173 -0
  29. package/src/creator/src/utils/creationRequest.test.ts +104 -0
  30. package/src/creator/src/utils/creationRequest.ts +63 -0
  31. package/src/creator/src/utils/store.ts +7 -0
  32. package/src/creatorDist/assets/{index-zrPponAn.css → index-B7s5Nmq8.css} +59 -0
  33. package/src/creatorDist/assets/{index-D6pmbMe9.js → index-GYxGB_X0.js} +22609 -22363
  34. package/src/creatorDist/index.html +2 -2
  35. package/src/ui/_app/app.css +1 -1
  36. package/src/ui/_app/app.js +2105 -2105
  37. package/src/ui/_app/sw.js +13 -0
  38. package/src/ui/app.tar.gz +0 -0
  39. package/src/utils/api.ts +157 -42
  40. package/src/utils/assetResolver.ts +368 -0
  41. package/src/utils/assetSync.ts +343 -0
  42. package/src/utils/awsCredentials.ts +57 -57
  43. package/src/utils/creatorUtilities.ts +16 -0
  44. package/src/utils/descriptions/publishStage.ts +394 -394
  45. package/src/utils/descriptions/resumePublication.ts +217 -217
  46. package/src/utils/descriptions/s3Storage.ts +214 -214
  47. package/src/utils/s3/packageManifestBackfill.ts +771 -771
@@ -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");
@@ -20,181 +19,55 @@ const rigoActions_1 = require("../utils/rigoActions");
20
19
  const misc_1 = require("../utils/misc");
21
20
  const creatorUtilities_1 = require("../utils/creatorUtilities");
22
21
  const branding_1 = require("../utils/branding");
22
+ const assetResolver_1 = require("../utils/assetResolver");
23
+ const assetSync_1 = require("../utils/assetSync");
23
24
  const uploadZipEndpont = api_1.RIGOBOT_HOST + "/v1/learnpack/upload";
24
- const getAvailableLangs = (learnJson) => {
25
- const langs = Object.keys((learnJson === null || learnJson === void 0 ? void 0 : learnJson.title) || {});
26
- return langs.filter((l) => typeof l === "string" && l.length > 0);
27
- };
28
25
  const getDefaultLang = (learnJson) => {
29
- const availableLangs = getAvailableLangs(learnJson);
26
+ const availableLangs = (0, assetResolver_1.getAvailableLangs)(learnJson);
30
27
  if (availableLangs.length === 0)
31
28
  return "en";
32
29
  return availableLangs.includes("en") ? "en" : availableLangs[0];
33
30
  };
34
- const getLocalizedValue = (translations, lang, fallbackLangs = ["en", "us"]) => {
35
- if (!translations || typeof translations !== "object")
36
- return "";
37
- const direct = translations[lang];
38
- if (typeof direct === "string" && direct.trim().length > 0)
39
- return direct;
40
- for (const fb of fallbackLangs) {
41
- const v = translations[fb];
42
- if (typeof v === "string" && v.trim().length > 0)
43
- return v;
44
- }
45
- const firstKey = Object.keys(translations)[0];
46
- const first = firstKey ? translations[firstKey] : "";
47
- return typeof first === "string" ? first : "";
48
- };
49
- const getExistingAssetsInfo = async (token, learnJson) => {
50
- const availableLangs = getAvailableLangs(learnJson);
51
- const results = [];
52
- for (const lang of availableLangs) {
53
- const assetTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, lang);
54
- if (!assetTitle)
55
- continue;
56
- let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
57
- slug = `${slug}-${lang}`;
58
- // eslint-disable-next-line no-await-in-loop
59
- const { exists, academyId } = await api_1.default.doesAssetExists(token, slug);
60
- results.push({ lang, slug, exists, academyId });
61
- }
62
- return results;
63
- };
64
- const determinePublishAcademyMode = (existingAssets) => {
65
- const academyIds = existingAssets
66
- .filter((a) => a.exists && a.academyId !== undefined)
67
- .map((a) => a.academyId);
68
- const unique = [...new Set(academyIds)];
69
- if (unique.length === 0)
70
- return { type: "select" };
71
- if (unique.length === 1)
72
- return { type: "locked", academyId: unique[0] };
73
- return { type: "conflict", academies: unique };
74
- };
75
- const handleAssetCreation = async (sessionPayload, learnJson, selectedLang, learnpackDeployUrl, b64IndexReadme, academyId, learnpackId, preflightInfo, all_translations = []) => {
76
- const category = "uncategorized";
31
+ const readReadmeFromDisk = async (lang) => {
32
+ const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
77
33
  try {
78
- const user = await api_1.default.validateToken(sessionPayload.token);
79
- const assetTitle = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.title, selectedLang);
80
- const assetDescription = getLocalizedValue(learnJson === null || learnJson === void 0 ? void 0 : learnJson.description, selectedLang);
81
- if (!assetTitle) {
82
- throw new Error(`Missing learn.json title for language "${selectedLang}"`);
83
- }
84
- let slug = (0, creatorUtilities_1.slugify)(assetTitle).slice(0, 47);
85
- slug = `${slug}-${selectedLang}`;
86
- // Use pre-flight info when available to avoid an extra GET request
87
- const { exists, academyId: existingAcademyId } = preflightInfo !== null && preflightInfo !== void 0 ? preflightInfo : (await api_1.default.doesAssetExists(sessionPayload.token, slug));
88
- // const technologies: unknown[] = Array.isArray(learnJson?.technologies) ?
89
- // learnJson.technologies :
90
- // []
91
- if (!exists) {
92
- console_1.default.info("Asset does not exist in this academy, creating it");
93
- const assetPayload = {
94
- slug: slug,
95
- title: assetTitle,
96
- lang: selectedLang,
97
- graded: true,
98
- description: assetDescription,
99
- learnpack_deploy_url: learnpackDeployUrl,
100
- // technologies: technologies.map((tech: unknown) =>
101
- // String(tech).toLowerCase().replace(/\s+/g, "-")
102
- // ),
103
- technologies: [],
104
- url: learnpackDeployUrl,
105
- category: category,
106
- owner: user.id,
107
- author: user.id,
108
- preview: learnJson.preview,
109
- readme_raw: b64IndexReadme,
110
- all_translations,
111
- learnpack_id: learnpackId,
112
- };
113
- if (academyId !== undefined) {
114
- assetPayload.academy_id = academyId;
115
- }
116
- const asset = await api_1.default.createAsset(sessionPayload.token, assetPayload);
117
- try {
118
- await api_1.default.updateRigoPackage(sessionPayload.rigobotToken.trim(), learnJson.slug, {
119
- asset_id: asset.id,
120
- });
121
- }
122
- catch (error) {
123
- console_1.default.error("Error updating Rigo package:", error);
124
- }
125
- console_1.default.info("Asset created with id", asset.id);
126
- return asset;
127
- }
128
- console_1.default.info("Asset exists, updating it");
129
- const updatePayload = {
130
- graded: true,
131
- learnpack_deploy_url: learnpackDeployUrl,
132
- title: assetTitle,
133
- category: category,
134
- description: assetDescription,
135
- all_translations,
136
- learnpack_id: learnpackId,
137
- };
138
- // Only set academy when the asset has none yet and the user selected one
139
- if (existingAcademyId === undefined && academyId !== undefined) {
140
- updatePayload.academy_id = academyId;
141
- }
142
- const asset = await api_1.default.updateAsset(sessionPayload.token, slug, updatePayload);
143
- try {
144
- await api_1.default.updateRigoPackage(sessionPayload.rigobotToken.trim(), learnJson.slug, {
145
- asset_id: asset.id,
146
- });
147
- }
148
- catch (error) {
149
- console_1.default.error("Error updating Rigo package:", error);
150
- }
151
- console_1.default.info("Asset updated with id", asset.id);
152
- return asset;
34
+ return fs.existsSync(readmePath) ?
35
+ fs.readFileSync(readmePath, "utf-8") :
36
+ "";
153
37
  }
154
38
  catch (error) {
155
- console_1.default.error("Error updating or creating asset:", error);
156
- throw error;
39
+ console_1.default.error("Error reading index readme:", error);
40
+ return "";
157
41
  }
158
42
  };
159
- exports.handleAssetCreation = handleAssetCreation;
160
- const createMultiLangAssetFromDisk = async (sessionPayload, learnJson, deployUrl, selectedAcademyId, existingAssetsInfo = []) => {
161
- const availableLangs = getAvailableLangs(learnJson);
162
- if (availableLangs.length === 0) {
43
+ const syncAssetsFromDisk = async (sessionPayload, learnJson, deployUrl, selectedAcademyId, resolution) => {
44
+ if ((0, assetResolver_1.getAvailableLangs)(learnJson).length === 0) {
163
45
  console_1.default.error("No languages found in learn.json.title. Add at least one language (e.g. title.en).");
164
46
  return;
165
47
  }
166
- const learnpackId = await api_1.default.resolveLearnpackPackageId(sessionPayload.rigobotToken, learnJson.slug);
167
- if (learnpackId === null) {
168
- console_1.default.warning("Breathecode assets skipped: could not resolve Learnpack package id");
169
- 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}`);
170
64
  }
171
- const all_translations = [];
172
- for (const lang of availableLangs) {
173
- const readmePath = path.join(process.cwd(), `README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
174
- let indexReadmeString = "";
175
- try {
176
- if (fs.existsSync(readmePath)) {
177
- indexReadmeString = fs.readFileSync(readmePath, "utf-8");
178
- }
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}`);
179
68
  }
180
- catch (error) {
181
- console_1.default.error("Error reading index readme:", error);
182
- indexReadmeString = "";
183
- }
184
- const b64IndexReadme = Buffer.from(indexReadmeString).toString("base64");
185
- const preflightInfo = existingAssetsInfo.find((a) => a.lang === lang);
186
- try {
187
- // eslint-disable-next-line no-await-in-loop
188
- const asset = await (0, exports.handleAssetCreation)(sessionPayload, learnJson, lang, deployUrl, b64IndexReadme, selectedAcademyId, learnpackId, preflightInfo, all_translations);
189
- if (!asset) {
190
- console_1.default.debug("Could not create/update asset for lang", lang);
191
- continue;
192
- }
193
- all_translations.push(asset.slug);
194
- }
195
- catch (error) {
196
- console_1.default.error("Error creating asset for language", lang, error);
197
- // Continue with other languages
69
+ else {
70
+ console_1.default.error(error.error.detail);
198
71
  }
199
72
  }
200
73
  };
@@ -313,8 +186,19 @@ class BuildCommand extends SessionCommand_1.default {
313
186
  process.exit(1);
314
187
  }
315
188
  console_1.default.info("Checking existing assets...");
316
- const existingAssetsInfo = await getExistingAssetsInfo(sessionPayload.token, learnJson);
317
- 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);
318
202
  let selectedAcademyId;
319
203
  if (academyMode.type === "conflict") {
320
204
  console_1.default.warning(`Some of your assets are associated with different academies ` +
@@ -380,9 +264,9 @@ class BuildCommand extends SessionCommand_1.default {
380
264
  if (fs.existsSync(indexHtmlPath)) {
381
265
  let indexHtmlContent = fs.readFileSync(indexHtmlPath, "utf-8");
382
266
  const selectedLang = getDefaultLang(learnJson);
383
- 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) ||
384
268
  "LearnPack is awesome!";
385
- 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) ||
386
270
  "LearnPack: Interactive Learning as a Service";
387
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
@@ -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)) {
@@ -31,7 +31,8 @@ const misc_1 = require("../utils/misc");
31
31
  const configBuilder_1 = require("../utils/configBuilder");
32
32
  const creatorUtilities_2 = require("../utils/creatorUtilities");
33
33
  const sidebarGenerator_1 = require("../utils/sidebarGenerator");
34
- const publish_1 = require("./publish");
34
+ const assetResolver_1 = require("../utils/assetResolver");
35
+ const assetSync_1 = require("../utils/assetSync");
35
36
  const export_1 = require("../utils/export");
36
37
  const router_1 = require("../services/ingest/http/router");
37
38
  const bucketIo_1 = require("../utils/coursePackage/bucketIo");
@@ -319,94 +320,34 @@ const cleanFormState = (formState) => {
319
320
  const cleanFormStateForSyllabus = (formState) => {
320
321
  return Object.assign(Object.assign({}, formState), { description: formState.description, technologies: formState.technologies, contentIndex: formState.contentIndex, purposse: undefined, duration: undefined, hasContentIndex: undefined, variables: undefined, currentStep: undefined, language: undefined, isCompleted: undefined });
321
322
  };
322
- const getLocalizedValue = (translations, lang, fallbackLangs = ["en", "us"]) => {
323
- if (!translations || typeof translations !== "object")
324
- return "";
325
- const direct = translations[lang];
326
- if (typeof direct === "string" && direct.trim().length > 0)
327
- return direct;
328
- for (const fb of fallbackLangs) {
329
- const v = translations[fb];
330
- if (typeof v === "string" && v.trim().length > 0)
331
- return v;
332
- }
333
- const firstKey = Object.keys(translations)[0];
334
- const first = firstKey ? translations[firstKey] : "";
335
- return typeof first === "string" ? first : "";
336
- };
337
- function assetSyncErrorDetail(err) {
338
- if (typeof (err === null || err === void 0 ? void 0 : err.detail) === "string")
339
- return err.detail;
340
- if (typeof (err === null || err === void 0 ? void 0 : err.message) === "string")
341
- return err.message;
342
- try {
343
- return JSON.stringify(err);
344
- }
345
- catch (_a) {
346
- return String(err);
347
- }
348
- }
349
323
  const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, courseJson, deployUrl, academyId) => {
350
- var _a;
351
- const learnpackId = await (0, api_1.resolveLearnpackPackageId)(rigoToken, courseSlug);
352
- if (learnpackId === null) {
353
- return {
354
- errors: [
355
- {
356
- kind: "package_error",
357
- error: {
358
- detail: "Could not resolve Learnpack package id; assets not synced to Breathecode.",
359
- },
360
- },
361
- ],
362
- };
363
- }
364
- const availableLangs = Object.keys(courseJson.title);
365
- console.log("AVAILABLE LANGUAGES to upload asset", availableLangs);
366
- const all_translations = [];
367
- const errors = [];
368
- for (const lang of availableLangs) {
369
- // eslint-disable-next-line no-await-in-loop
370
- const indexReadme = await bucket.file(`courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
371
- let indexReadmeString = "";
324
+ const readReadme = async (lang) => {
325
+ const indexReadme = bucket.file(`courses/${courseSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(lang)}`);
372
326
  try {
373
- // eslint-disable-next-line no-await-in-loop
374
- const [indexReadmeContent] = await indexReadme.download();
375
- indexReadmeString = indexReadmeContent.toString();
327
+ const [content] = await indexReadme.download();
328
+ return content.toString();
376
329
  }
377
330
  catch (error) {
378
331
  console.error("Error downloading index readme", error);
379
332
  // TODO: Trigger generation of the index readme
380
- indexReadmeString = "";
381
- }
382
- const b64IndexReadme = buffer_1.Buffer.from(indexReadmeString).toString("base64");
383
- try {
384
- // eslint-disable-next-line no-await-in-loop
385
- const asset = await (0, publish_1.handleAssetCreation)({ token: bcToken, rigobotToken: rigoToken.trim() }, courseJson, lang, deployUrl, b64IndexReadme, academyId, learnpackId, undefined, all_translations);
386
- if (!asset) {
387
- errors.push({
388
- kind: "lang_error",
389
- lang,
390
- error: { detail: "Failed to create asset" },
391
- });
392
- console.log("No se pudo crear el asset, saltando idioma", lang);
393
- continue;
394
- }
395
- all_translations.push(asset.slug);
396
- }
397
- catch (error) {
398
- const errorData = error && typeof error === "object" && "response" in error ?
399
- ((_a = error.response) === null || _a === void 0 ? void 0 : _a.data) || error :
400
- error;
401
- errors.push({
402
- kind: "lang_error",
403
- lang,
404
- error: { detail: assetSyncErrorDetail(errorData) },
405
- });
406
- console.error(`Error creating asset for language ${lang}:`, error);
333
+ return "";
407
334
  }
335
+ };
336
+ const result = await (0, assetSync_1.syncPackageAssets)({
337
+ bcToken,
338
+ rigoToken,
339
+ packageSlug: courseSlug,
340
+ learnJson: courseJson,
341
+ deployUrl,
342
+ academyId,
343
+ readReadme,
344
+ });
345
+ for (const warning of result.warnings)
346
+ console.warn("[assets]", warning);
347
+ for (const synced of result.synced) {
348
+ console.log(`[assets] ${synced.action} "${synced.lang}": #${synced.asset.id} ${synced.asset.slug}`);
408
349
  }
409
- return { errors };
350
+ return { errors: result.errors };
410
351
  };
411
352
  const lessonCleaner = (lesson) => {
412
353
  return Object.assign(Object.assign({}, lesson), { duration: undefined, generated: undefined, status: undefined, translations: undefined, uid: undefined, initialContent: undefined, locked: undefined });
@@ -3713,52 +3654,47 @@ class ServeCommand extends SessionCommand_1.default {
3713
3654
  error: "Authentication failed, missing breathecode token",
3714
3655
  });
3715
3656
  }
3657
+ // Older IDEs only send the Breathecode token; without the Rigobot
3658
+ // token the lookup falls back to the title-derived slugs.
3659
+ const rigoToken = req.header("x-rigo-token");
3716
3660
  const configFile = await bucket.file(`courses/${slug}/.learn/config.json`);
3717
3661
  const [configContent] = await configFile.download();
3718
3662
  const configJson = JSON.parse(configContent.toString());
3719
3663
  const { config } = configJson;
3720
- const availableLangs = Object.keys(config.title || {});
3721
- let isPublished = false;
3722
- // Collect academy ids from all existing assets
3723
- const foundAcademyIds = [];
3724
- const slugsToCheck = availableLangs
3725
- .map(lang => {
3726
- const assetTitle = getLocalizedValue(config.title, lang);
3727
- if (!assetTitle)
3728
- return null;
3729
- const assetSlug = (0, creatorUtilities_2.slugify)(assetTitle).slice(0, 47);
3730
- return `${assetSlug}-${lang}`;
3731
- })
3732
- .filter((slug) => slug !== null);
3733
- const assetChecks = await Promise.all(slugsToCheck.map(slug => (0, api_1.doesAssetExists)(bcToken, slug)));
3734
- for (const { exists, academyId: existingAcademyId } of assetChecks) {
3735
- if (exists) {
3736
- isPublished = true;
3737
- if (existingAcademyId !== undefined) {
3738
- foundAcademyIds.push(existingAcademyId);
3739
- }
3664
+ const resolution = await (0, assetResolver_1.resolvePackageAssets)({
3665
+ bcToken,
3666
+ rigoToken,
3667
+ packageSlug: slug,
3668
+ learnJson: config,
3669
+ });
3670
+ for (const warning of resolution.warnings) {
3671
+ console.warn("[package-academy]", warning);
3672
+ }
3673
+ const isPublished = (0, assetResolver_1.hasPublishedAsset)(resolution);
3674
+ const academyMode = (0, assetResolver_1.determineAcademyMode)(resolution);
3675
+ const warnings = [...resolution.warnings];
3676
+ if (resolution.packageState === "unknown") {
3677
+ warnings.push(`${resolution.packageError}; existing assets could not be checked.`);
3678
+ }
3679
+ for (const [lang, entry] of resolution.byLang) {
3680
+ if (entry.status === "unknown") {
3681
+ warnings.push(`"${lang}": ${entry.error}`);
3682
+ }
3683
+ else if (entry.status === "ambiguous") {
3684
+ warnings.push(`"${lang}": ${entry.candidates.length} assets found (${entry.candidates
3685
+ .map(c => `#${c.id} ${c.slug}`)
3686
+ .join(", ")})`);
3740
3687
  }
3741
3688
  }
3742
- const uniqueAcademies = [...new Set(foundAcademyIds)];
3743
- let mode;
3744
- let lockedAcademyId;
3745
- let conflictAcademies;
3746
- if (uniqueAcademies.length === 0) {
3747
- mode = "select";
3748
- }
3749
- else if (uniqueAcademies.length === 1) {
3750
- mode = "locked";
3751
- lockedAcademyId = uniqueAcademies[0];
3752
- }
3753
- else {
3754
- mode = "conflict";
3755
- conflictAcademies = uniqueAcademies;
3756
- }
3689
+ const mode = academyMode.type;
3690
+ const lockedAcademyId = academyMode.type === "locked" ? academyMode.academyId : undefined;
3691
+ const conflictAcademies = academyMode.type === "conflict" ? academyMode.academies : undefined;
3757
3692
  return res.json({
3758
3693
  isPublished,
3759
3694
  mode,
3760
3695
  lockedAcademyId,
3761
3696
  conflictAcademies,
3697
+ warnings,
3762
3698
  });
3763
3699
  }
3764
3700
  catch (error) {
@@ -65,6 +65,7 @@
65
65
  --color-blue-50: oklch(97% 0.014 254.604);
66
66
  --color-blue-100: oklch(93.2% 0.032 255.585);
67
67
  --color-blue-200: oklch(88.2% 0.059 254.128);
68
+ --color-blue-300: oklch(80.9% 0.105 251.813);
68
69
  --color-blue-400: oklch(70.7% 0.165 254.624);
69
70
  --color-blue-500: oklch(62.3% 0.214 259.815);
70
71
  --color-blue-600: oklch(54.6% 0.245 262.881);
@@ -439,6 +440,12 @@
439
440
  .z-1000 {
440
441
  z-index: 1000;
441
442
  }
443
+ .col-span-2 {
444
+ grid-column: span 2 / span 2;
445
+ }
446
+ .row-start-2 {
447
+ grid-row-start: 2;
448
+ }
442
449
  .container {
443
450
  width: 100%;
444
451
  }
@@ -536,6 +543,9 @@
536
543
  .flex {
537
544
  display: flex;
538
545
  }
546
+ .grid {
547
+ display: grid;
548
+ }
539
549
  .hidden {
540
550
  display: none;
541
551
  }
@@ -548,6 +558,9 @@
548
558
  .table {
549
559
  display: table;
550
560
  }
561
+ .field-sizing-content {
562
+ field-sizing: content;
563
+ }
551
564
  .h-2 {
552
565
  height: calc(var(--spacing) * 2);
553
566
  }
@@ -644,6 +657,9 @@
644
657
  .w-full {
645
658
  width: 100%;
646
659
  }
660
+ .max-w-64 {
661
+ max-width: calc(var(--spacing) * 64);
662
+ }
647
663
  .max-w-\[700px\] {
648
664
  max-width: 700px;
649
665
  }
@@ -705,6 +721,9 @@
705
721
  .list-disc {
706
722
  list-style-type: disc;
707
723
  }
724
+ .grid-cols-2 {
725
+ grid-template-columns: repeat(2, minmax(0, 1fr));
726
+ }
708
727
  .flex-col {
709
728
  flex-direction: column;
710
729
  }
@@ -780,6 +799,15 @@
780
799
  calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))
781
800
  );
782
801
  }
802
+ .justify-self-center {
803
+ justify-self: center;
804
+ }
805
+ .justify-self-end {
806
+ justify-self: flex-end;
807
+ }
808
+ .justify-self-start {
809
+ justify-self: flex-start;
810
+ }
783
811
  .truncate {
784
812
  text-overflow: ellipsis;
785
813
  white-space: nowrap;
@@ -905,6 +933,9 @@
905
933
  .bg-red-100 {
906
934
  background-color: var(--color-red-100);
907
935
  }
936
+ .bg-transparent {
937
+ background-color: #0000;
938
+ }
908
939
  .bg-white {
909
940
  background-color: var(--color-white);
910
941
  }
@@ -1046,6 +1077,9 @@
1046
1077
  .break-words {
1047
1078
  overflow-wrap: break-word;
1048
1079
  }
1080
+ .whitespace-nowrap {
1081
+ white-space: nowrap;
1082
+ }
1049
1083
  .whitespace-pre-line {
1050
1084
  white-space: pre-line;
1051
1085
  }
@@ -1244,6 +1278,19 @@
1244
1278
  .focus\:ring-blue-200:focus {
1245
1279
  --tw-ring-color: var(--color-blue-200);
1246
1280
  }
1281
+ .focus\:outline-none:focus {
1282
+ --tw-outline-style: none;
1283
+ outline-style: none;
1284
+ }
1285
+ .focus-visible\:ring-2:focus-visible {
1286
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0
1287
+ calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentcolor);
1288
+ box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow),
1289
+ var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
1290
+ }
1291
+ .focus-visible\:ring-blue-300:focus-visible {
1292
+ --tw-ring-color: var(--color-blue-300);
1293
+ }
1247
1294
  .disabled\:cursor-not-allowed:disabled {
1248
1295
  cursor: not-allowed;
1249
1296
  }
@@ -1257,9 +1304,21 @@
1257
1304
  opacity: 0.5;
1258
1305
  }
1259
1306
  @media (min-width: 40rem) {
1307
+ .sm\:col-span-1 {
1308
+ grid-column: span 1 / span 1;
1309
+ }
1310
+ .sm\:col-start-2 {
1311
+ grid-column-start: 2;
1312
+ }
1313
+ .sm\:row-start-1 {
1314
+ grid-row-start: 1;
1315
+ }
1260
1316
  .sm\:w-auto {
1261
1317
  width: auto;
1262
1318
  }
1319
+ .sm\:grid-cols-\[1fr_auto_1fr\] {
1320
+ grid-template-columns: 1fr auto 1fr;
1321
+ }
1263
1322
  .sm\:flex-row {
1264
1323
  flex-direction: row;
1265
1324
  }