@learnpack/learnpack 5.0.319 → 5.0.322
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/serve.js +85 -37
- package/lib/creatorDist/assets/{index-XZDcEWl9.js → index-BhqDgBS9.js} +588 -585
- package/lib/creatorDist/index.html +1 -1
- package/lib/utils/checkNotInstalled.d.ts +1 -1
- package/lib/utils/checkNotInstalled.js +80 -22
- package/lib/utils/errorHandler.d.ts +97 -0
- package/lib/utils/errorHandler.js +239 -0
- package/lib/utils/rigoActions.js +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +3550 -3461
- package/src/creator/src/components/syllabus/SyllabusEditor.tsx +20 -9
- package/src/creatorDist/assets/{index-XZDcEWl9.js → index-BhqDgBS9.js} +588 -585
- package/src/creatorDist/index.html +1 -1
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +2285 -2306
- package/src/ui/app.tar.gz +0 -0
- package/src/utils/checkNotInstalled.ts +108 -32
- package/src/utils/errorHandler.ts +295 -0
- package/src/utils/rigoActions.ts +639 -639
package/lib/commands/serve.js
CHANGED
|
@@ -32,6 +32,7 @@ const creatorUtilities_2 = require("../utils/creatorUtilities");
|
|
|
32
32
|
const sidebarGenerator_1 = require("../utils/sidebarGenerator");
|
|
33
33
|
const publish_1 = require("./publish");
|
|
34
34
|
const export_1 = require("../utils/export");
|
|
35
|
+
const errorHandler_1 = require("../utils/errorHandler");
|
|
35
36
|
const frontMatter = require("front-matter");
|
|
36
37
|
if (process.env.NEW_RELIC_ENABLED === "true") {
|
|
37
38
|
require("newrelic");
|
|
@@ -90,18 +91,18 @@ async function fetchComponentsYml() {
|
|
|
90
91
|
axios_1.default.get("https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/docs/assessment_components.yml"),
|
|
91
92
|
axios_1.default.get("https://raw.githubusercontent.com/learnpack/ide/refs/heads/master/docs/explanatory_components.yml"),
|
|
92
93
|
]);
|
|
93
|
-
const combinedContent = `
|
|
94
|
-
# ASSESSMENT COMPONENTS
|
|
95
|
-
These components are designed for evaluation and knowledge assessment:
|
|
96
|
-
|
|
97
|
-
${assessmentResponse.data}
|
|
98
|
-
|
|
99
|
-
---
|
|
100
|
-
|
|
101
|
-
# EXPLANATORY COMPONENTS
|
|
102
|
-
These components are designed for explanation and learning support:
|
|
103
|
-
|
|
104
|
-
${explanatoryResponse.data}
|
|
94
|
+
const combinedContent = `
|
|
95
|
+
# ASSESSMENT COMPONENTS
|
|
96
|
+
These components are designed for evaluation and knowledge assessment:
|
|
97
|
+
|
|
98
|
+
${assessmentResponse.data}
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
# EXPLANATORY COMPONENTS
|
|
103
|
+
These components are designed for explanation and learning support:
|
|
104
|
+
|
|
105
|
+
${explanatoryResponse.data}
|
|
105
106
|
`;
|
|
106
107
|
return combinedContent;
|
|
107
108
|
}
|
|
@@ -135,10 +136,10 @@ const createInitialSidebar = async (slugs, initialLanguage = "en") => {
|
|
|
135
136
|
return sidebar;
|
|
136
137
|
};
|
|
137
138
|
const uploadInitialReadme = async (bucket, exSlug, targetDir, packageContext) => {
|
|
138
|
-
const isGeneratingText = `
|
|
139
|
-
\`\`\`loader slug="${exSlug}"
|
|
140
|
-
:rigo
|
|
141
|
-
\`\`\`
|
|
139
|
+
const isGeneratingText = `
|
|
140
|
+
\`\`\`loader slug="${exSlug}"
|
|
141
|
+
:rigo
|
|
142
|
+
\`\`\`
|
|
142
143
|
`;
|
|
143
144
|
const readmeFilename = `README${(0, creatorUtilities_1.getReadmeExtension)(packageContext.language || "en")}`;
|
|
144
145
|
await uploadFileToBucket(bucket, isGeneratingText, `${targetDir}/${readmeFilename}`);
|
|
@@ -148,7 +149,7 @@ const cleanFormState = (formState) => {
|
|
|
148
149
|
return rest;
|
|
149
150
|
};
|
|
150
151
|
const cleanFormStateForSyllabus = (formState) => {
|
|
151
|
-
return Object.assign(Object.assign({}, formState), { description: formState.description, technologies: formState.technologies, purposse: undefined, duration: undefined, hasContentIndex: undefined, variables: undefined, currentStep: undefined, language: undefined, isCompleted: undefined });
|
|
152
|
+
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 });
|
|
152
153
|
};
|
|
153
154
|
const createMultiLangAsset = async (bucket, rigoToken, bcToken, courseSlug, courseJson, deployUrl) => {
|
|
154
155
|
const availableLangs = Object.keys(courseJson.title);
|
|
@@ -182,6 +183,13 @@ const lessonCleaner = (lesson) => {
|
|
|
182
183
|
return Object.assign(Object.assign({}, lesson), { duration: undefined, generated: undefined, status: undefined, translations: undefined, uid: undefined, initialContent: undefined, locked: undefined });
|
|
183
184
|
};
|
|
184
185
|
async function startInitialContentGeneration(rigoToken, steps, packageContext, exercise, courseSlug, purposeSlug, lastLesson = "") {
|
|
186
|
+
// Defensive validation
|
|
187
|
+
if (!exercise) {
|
|
188
|
+
throw new errorHandler_1.ValidationError("Exercise is required but was not provided");
|
|
189
|
+
}
|
|
190
|
+
if (!exercise.id || !exercise.title) {
|
|
191
|
+
throw new errorHandler_1.ValidationError(`Exercise is missing required properties: id=${exercise.id}, title=${exercise.title}`);
|
|
192
|
+
}
|
|
185
193
|
const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
|
|
186
194
|
console.log("Starting initial content generation for", exSlug);
|
|
187
195
|
const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`;
|
|
@@ -649,9 +657,8 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
649
657
|
return res.status(404).json({ error: "Exercise not found" });
|
|
650
658
|
}
|
|
651
659
|
const exerciseDir = `courses/${courseSlug}/exercises/${exercise.slug}`;
|
|
652
|
-
for (const
|
|
660
|
+
for (const fileObj of files) {
|
|
653
661
|
try {
|
|
654
|
-
const fileObj = JSON.parse(fileStr);
|
|
655
662
|
console.log(`📄 Processing file: ${fileObj.name}`);
|
|
656
663
|
// Save the main file with content
|
|
657
664
|
if (fileObj.name && fileObj.content) {
|
|
@@ -696,7 +703,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
696
703
|
res.status(500).json({ error: error.message });
|
|
697
704
|
}
|
|
698
705
|
});
|
|
699
|
-
app.post("/actions/continue-generating/:courseSlug/:lessonUid", async (req, res) => {
|
|
706
|
+
app.post("/actions/continue-generating/:courseSlug/:lessonUid", (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
700
707
|
const { courseSlug, lessonUid } = req.params;
|
|
701
708
|
const { feedback, mode } = req.body;
|
|
702
709
|
const rigoToken = req.header("x-rigo-token");
|
|
@@ -706,13 +713,23 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
706
713
|
// console.log("FEEDBACK", feedback);
|
|
707
714
|
// console.log("MODE", mode);
|
|
708
715
|
if (!rigoToken) {
|
|
709
|
-
|
|
710
|
-
error: "Rigo token is required. x-rigo-token header is missing",
|
|
711
|
-
});
|
|
716
|
+
throw new errorHandler_1.ValidationError("Rigo token is required. x-rigo-token header is missing");
|
|
712
717
|
}
|
|
713
718
|
const syllabus = await getSyllabus(courseSlug, bucket);
|
|
714
719
|
const exercise = syllabus.lessons.find(lesson => lesson.uid === lessonUid);
|
|
715
720
|
const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUid);
|
|
721
|
+
// Validate that exercise exists
|
|
722
|
+
if (!exercise) {
|
|
723
|
+
throw new errorHandler_1.NotFoundError(`Lesson with UID "${lessonUid}" not found in course "${courseSlug}"`);
|
|
724
|
+
}
|
|
725
|
+
// Validate that exercise index is valid (defensive check)
|
|
726
|
+
if (exerciseIndex === -1) {
|
|
727
|
+
throw new errorHandler_1.NotFoundError(`Lesson with UID "${lessonUid}" not found in course "${courseSlug}"`);
|
|
728
|
+
}
|
|
729
|
+
// Validate that exercise has required properties
|
|
730
|
+
if (!exercise.id || !exercise.title) {
|
|
731
|
+
throw new errorHandler_1.ValidationError(`Lesson "${lessonUid}" is missing required properties (id or title)`);
|
|
732
|
+
}
|
|
716
733
|
// previous exercise
|
|
717
734
|
let previousReadme = "---";
|
|
718
735
|
const previousExercise = syllabus.lessons[exerciseIndex - 1];
|
|
@@ -753,7 +770,7 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
753
770
|
syllabus.generationMode = mode;
|
|
754
771
|
await saveSyllabus(courseSlug, syllabus, bucket);
|
|
755
772
|
res.json({ status: "SUCCESS" });
|
|
756
|
-
});
|
|
773
|
+
}));
|
|
757
774
|
// TODO: Check if this command is being used
|
|
758
775
|
app.post("/actions/generate-image/:courseSlug", async (req, res) => {
|
|
759
776
|
const rigoToken = req.header("x-rigo-token");
|
|
@@ -1383,22 +1400,49 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
1383
1400
|
res.set("Content-Disposition", `attachment; filename="${file}"`);
|
|
1384
1401
|
fileStream.pipe(res);
|
|
1385
1402
|
});
|
|
1386
|
-
app.put("/exercise/:slug/file/:fileName", express.text(), async (req, res) => {
|
|
1403
|
+
app.put("/exercise/:slug/file/:fileName", express.text(), (0, errorHandler_1.asyncHandler)(async (req, res) => {
|
|
1404
|
+
var _a, _b;
|
|
1387
1405
|
const { slug, fileName } = req.params;
|
|
1388
1406
|
const query = req.query;
|
|
1389
|
-
console.log(`PUT /exercise/${slug}/file/${fileName}`);
|
|
1390
1407
|
const courseSlug = query.slug;
|
|
1391
|
-
|
|
1392
|
-
//
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1408
|
+
console.log(`PUT /exercise/${slug}/file/${fileName}`);
|
|
1409
|
+
// Validate required parameters
|
|
1410
|
+
if (!courseSlug) {
|
|
1411
|
+
throw new errorHandler_1.ValidationError("Course slug is required in query parameters");
|
|
1412
|
+
}
|
|
1413
|
+
if (!fileName || !slug) {
|
|
1414
|
+
throw new errorHandler_1.ValidationError("File name and exercise slug are required");
|
|
1415
|
+
}
|
|
1416
|
+
try {
|
|
1417
|
+
// Update the file in the bucket
|
|
1418
|
+
const file = bucket.file(`courses/${courseSlug}/exercises/${slug}/${fileName}`);
|
|
1419
|
+
await file.save(req.body, {
|
|
1420
|
+
resumable: false,
|
|
1421
|
+
});
|
|
1422
|
+
const created = await file.exists();
|
|
1423
|
+
res.send({
|
|
1424
|
+
message: "File updated",
|
|
1425
|
+
created,
|
|
1426
|
+
});
|
|
1427
|
+
}
|
|
1428
|
+
catch (error) {
|
|
1429
|
+
// Handle Google Cloud Storage rate limit errors (429)
|
|
1430
|
+
if (error.code === 429 ||
|
|
1431
|
+
((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("rate limit")) ||
|
|
1432
|
+
((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("rateLimitExceeded"))) {
|
|
1433
|
+
throw new errorHandler_1.ConflictError("Storage rate limit exceeded. Please try again in a few moments.", {
|
|
1434
|
+
code: "STORAGE_RATE_LIMIT",
|
|
1435
|
+
retryAfter: 60, // seconds
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
// Handle other GCS errors
|
|
1439
|
+
if (error.code) {
|
|
1440
|
+
throw new errorHandler_1.InternalServerError(`Storage error: ${error.message || "Failed to update file"}`, { code: error.code });
|
|
1441
|
+
}
|
|
1442
|
+
// Re-throw if it's already an operational error
|
|
1443
|
+
throw error;
|
|
1444
|
+
}
|
|
1445
|
+
}));
|
|
1402
1446
|
// Create a new step for a course
|
|
1403
1447
|
app.post("/course/:slug/create-step", async (req, res) => {
|
|
1404
1448
|
console.log("POST /course/:slug/create-step");
|
|
@@ -2393,6 +2437,10 @@ class ServeCommand extends SessionCommand_1.default {
|
|
|
2393
2437
|
.json({ error: "Export failed", details: error.message });
|
|
2394
2438
|
}
|
|
2395
2439
|
});
|
|
2440
|
+
// 404 error handler
|
|
2441
|
+
app.use(errorHandler_1.notFoundHandler);
|
|
2442
|
+
// Global error handler
|
|
2443
|
+
app.use(errorHandler_1.errorHandler);
|
|
2396
2444
|
server.listen(PORT, () => {
|
|
2397
2445
|
console.log(`🚀 Creator UI server running at http://localhost:${PORT}/creator`);
|
|
2398
2446
|
});
|