@learnpack/learnpack 5.0.358 → 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.
- package/lib/commands/publish.d.ts +0 -10
- package/lib/commands/publish.js +52 -168
- package/lib/commands/serve.js +52 -116
- package/lib/utils/api.d.ts +62 -15
- package/lib/utils/api.js +124 -45
- package/lib/utils/assetResolver.d.ts +116 -0
- package/lib/utils/assetResolver.js +268 -0
- package/lib/utils/assetSync.d.ts +48 -0
- package/lib/utils/assetSync.js +267 -0
- package/lib/utils/creatorUtilities.d.ts +1 -0
- package/lib/utils/creatorUtilities.js +17 -1
- package/package.json +1 -1
- package/src/commands/publish.ts +61 -255
- package/src/commands/serve.ts +65 -140
- package/src/ui/_app/app.js +1784 -1784
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/api.ts +157 -42
- package/src/utils/assetResolver.ts +368 -0
- package/src/utils/assetSync.ts +343 -0
- package/src/utils/awsCredentials.ts +57 -57
- package/src/utils/creatorUtilities.ts +16 -0
- package/src/utils/descriptions/publishStage.ts +394 -394
- package/src/utils/descriptions/resumePublication.ts +217 -217
- package/src/utils/descriptions/s3Storage.ts +214 -214
- 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: {
|
package/lib/commands/publish.js
CHANGED
|
@@ -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
|
|
35
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|
156
|
-
|
|
39
|
+
console_1.default.error("Error reading index readme:", error);
|
|
40
|
+
return "";
|
|
157
41
|
}
|
|
158
42
|
};
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
181
|
-
console_1.default.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
|
|
317
|
-
|
|
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
|
|
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)) {
|
package/lib/commands/serve.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
351
|
-
|
|
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
|
-
|
|
374
|
-
|
|
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
|
-
|
|
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
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
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
|
|
3743
|
-
|
|
3744
|
-
|
|
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) {
|
package/lib/utils/api.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const BREATHECODE_HOST = "https://breathecode.herokuapp.com";
|
|
1
2
|
/**
|
|
2
3
|
* Student telemetry, and publication events. Both share the endpoint: a body
|
|
3
4
|
* carrying a top-level `event` key is dispatched as an event, anything else
|
|
@@ -31,13 +32,30 @@ export type AssetSyncError = {
|
|
|
31
32
|
detail: string;
|
|
32
33
|
};
|
|
33
34
|
};
|
|
35
|
+
export type RigoPackageLookup = {
|
|
36
|
+
kind: "found";
|
|
37
|
+
id: number;
|
|
38
|
+
assetIds: number[];
|
|
39
|
+
} | {
|
|
40
|
+
kind: "missing";
|
|
41
|
+
} | {
|
|
42
|
+
kind: "error";
|
|
43
|
+
error: string;
|
|
44
|
+
};
|
|
34
45
|
/**
|
|
35
|
-
*
|
|
36
|
-
* @param
|
|
46
|
+
* Human-readable summary of a failed axios call, for warnings and error payloads.
|
|
47
|
+
* @param error - Whatever the request rejected with.
|
|
48
|
+
* @returns "HTTP <status>" when the server answered, the error message otherwise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function describeRequestError(error: unknown): string;
|
|
51
|
+
/**
|
|
52
|
+
* GET a Rigobot package by slug. Distinguishes "no such package" from
|
|
53
|
+
* "could not check", so callers can fail closed on the latter.
|
|
54
|
+
* @param rigoToken Rigobot API token (`Token` header value).
|
|
37
55
|
* @param courseSlug Learnpack package slug used in the Rigobot URL path.
|
|
38
|
-
* @returns
|
|
56
|
+
* @returns The package id and its asset_ids, `missing` on 404, `error` otherwise (403 included: a package owned by somebody else is not ours to read).
|
|
39
57
|
*/
|
|
40
|
-
export declare function
|
|
58
|
+
export declare function getRigoPackage(rigoToken: string, courseSlug: string): Promise<RigoPackageLookup>;
|
|
41
59
|
type TAssetMissing = {
|
|
42
60
|
slug: string;
|
|
43
61
|
title: string;
|
|
@@ -57,10 +75,41 @@ type TAssetMissing = {
|
|
|
57
75
|
learnpack_id: number;
|
|
58
76
|
};
|
|
59
77
|
export declare const createAsset: (token: string, asset: TAssetMissing) => Promise<any>;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
78
|
+
/** The subset of a Breathecode asset the publish flow reads. */
|
|
79
|
+
export type BcAsset = {
|
|
80
|
+
id: number;
|
|
81
|
+
slug: string;
|
|
82
|
+
lang: string | null;
|
|
83
|
+
status: string | null;
|
|
84
|
+
learnpackId: number | null;
|
|
85
|
+
learnpackDeployUrl: string | null;
|
|
86
|
+
ownerId: number | null;
|
|
87
|
+
academyId: number | null;
|
|
88
|
+
};
|
|
89
|
+
export type AssetLookup = {
|
|
90
|
+
kind: "found";
|
|
91
|
+
asset: BcAsset;
|
|
92
|
+
} | {
|
|
93
|
+
kind: "missing";
|
|
94
|
+
} | {
|
|
95
|
+
kind: "error";
|
|
96
|
+
error: string;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* GET a Breathecode asset by numeric id. The endpoint applies no status or
|
|
100
|
+
* visibility filter for a single asset, so this is the stable anchor.
|
|
101
|
+
* @param token Breathecode token.
|
|
102
|
+
* @param assetId Asset primary key.
|
|
103
|
+
* @returns `found` with the asset, `missing` on 404, `error` when it could not be checked.
|
|
104
|
+
*/
|
|
105
|
+
export declare const getAssetById: (token: string, assetId: number) => Promise<AssetLookup>;
|
|
106
|
+
/**
|
|
107
|
+
* GET a Breathecode asset by slug.
|
|
108
|
+
* @param token Breathecode token.
|
|
109
|
+
* @param assetSlug Asset slug.
|
|
110
|
+
* @returns `found` with the asset, `missing` on 404, `error` when it could not be checked.
|
|
111
|
+
*/
|
|
112
|
+
export declare const getAssetBySlug: (token: string, assetSlug: string) => Promise<AssetLookup>;
|
|
64
113
|
type TTechnology = {
|
|
65
114
|
slug: string;
|
|
66
115
|
lang: string;
|
|
@@ -83,18 +132,16 @@ declare const _default: {
|
|
|
83
132
|
listUserAcademies: (breathecodeToken: string) => Promise<TAcademy[]>;
|
|
84
133
|
validateToken: (token: string) => Promise<any>;
|
|
85
134
|
createAsset: (token: string, asset: TAssetMissing) => Promise<any>;
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
}>;
|
|
90
|
-
updateAsset: (token: string, assetSlug: string, asset: Partial<TAssetMissing> & {
|
|
135
|
+
getAssetById: (token: string, assetId: number) => Promise<AssetLookup>;
|
|
136
|
+
getAssetBySlug: (token: string, assetSlug: string) => Promise<AssetLookup>;
|
|
137
|
+
updateAsset: (token: string, assetId: number, asset: Partial<TAssetMissing> & {
|
|
91
138
|
learnpack_id: number;
|
|
92
139
|
}) => Promise<any>;
|
|
93
|
-
|
|
140
|
+
getRigoPackage: typeof getRigoPackage;
|
|
94
141
|
getCategories: (token: string) => Promise<any>;
|
|
95
142
|
updateRigoPackage: (token: string, slug: string, updates: {
|
|
96
143
|
asset_id?: number;
|
|
97
|
-
|
|
144
|
+
asset_ids?: number[];
|
|
98
145
|
}) => Promise<any>;
|
|
99
146
|
createRigoPackage: (token: string, slug: string, config: any) => Promise<any>;
|
|
100
147
|
getCurrentTechnologies: () => TTechnology[];
|