@learnpack/learnpack 5.0.320 → 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 +68 -20
- 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 +111 -23
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +344 -356
- 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");
|
|
@@ -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
|
});
|
|
@@ -4,5 +4,5 @@ type TNeededPlugins = {
|
|
|
4
4
|
notInstalled: string[];
|
|
5
5
|
};
|
|
6
6
|
export declare const checkNotInstalledPlugins: (exercises: IExercise[], installedPlugins: string[], command: any) => Promise<TNeededPlugins>;
|
|
7
|
-
export declare const checkNotInstalledDependencies: (neededPlugins: string[]) => Promise<boolean
|
|
7
|
+
export declare const checkNotInstalledDependencies: (neededPlugins: string[]) => Promise<boolean>;
|
|
8
8
|
export {};
|
|
@@ -114,18 +114,47 @@ const installDependencies = async (deps, packageManager) => {
|
|
|
114
114
|
else if (packageManager === "pip") {
|
|
115
115
|
command = `pip install ${deps.join(" ")}`;
|
|
116
116
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
117
|
+
try {
|
|
118
|
+
const { stdout, stderr } = await exec(command);
|
|
119
|
+
if (stderr &&
|
|
120
|
+
(stderr.includes("npm ERR!") || stderr.includes("Traceback"))) {
|
|
121
|
+
console_1.default.error(`Error executing ${command}.`);
|
|
122
|
+
console_1.default.error(stderr);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
console_1.default.info(`Dependencies ${deps.join(" ")} installed...`);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
// Si npm/pip retorna código > 0, exec lanzará una excepción
|
|
130
|
+
const execError = error;
|
|
131
|
+
const stdout = execError.stdout || "";
|
|
132
|
+
const stderr = execError.stderr || "";
|
|
133
|
+
const errorMessage = stderr || execError.message || String(error);
|
|
134
|
+
// Verificar si es un error real
|
|
135
|
+
if (errorMessage.includes("npm ERR!") ||
|
|
136
|
+
errorMessage.includes("Traceback")) {
|
|
137
|
+
console_1.default.error(`Error executing ${command}.`);
|
|
138
|
+
console_1.default.error(errorMessage);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Si no es un error real, podría ser solo un warning
|
|
142
|
+
// Verificar si la instalación fue exitosa a pesar del warning
|
|
143
|
+
if (stdout &&
|
|
144
|
+
(stdout.includes("added") || stdout.includes("Successfully installed"))) {
|
|
145
|
+
console_1.default.info(`Dependencies ${deps.join(" ")} installed...`);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
// Si no hay indicios de éxito, se asume fallo (retorna undefined implícitamente)
|
|
149
|
+
console_1.default.debug(`${command} returned non-zero exit code, but may be just a warning:`, errorMessage);
|
|
122
150
|
}
|
|
123
|
-
console_1.default.info(`Dependencies ${deps.join(" ")} installed...`);
|
|
124
|
-
return true;
|
|
125
151
|
};
|
|
126
152
|
const checkNotInstalledDependencies = async (neededPlugins) => {
|
|
127
153
|
console_1.default.info("Checking needed dependencies...");
|
|
128
|
-
const jsPluginsDependencies = [
|
|
154
|
+
const jsPluginsDependencies = [
|
|
155
|
+
"jest@29.7.0",
|
|
156
|
+
"jest-environment-jsdom@29.7.0",
|
|
157
|
+
];
|
|
129
158
|
// pytest up to 6.2.5
|
|
130
159
|
const pyPluginsDependencies = ["pytest", "pytest-testdox", "mock"];
|
|
131
160
|
const npmLsCommand = "npm ls jest jest-environment-jsdom -g";
|
|
@@ -141,14 +170,29 @@ const checkNotInstalledDependencies = async (neededPlugins) => {
|
|
|
141
170
|
pytestNeeded = true;
|
|
142
171
|
}
|
|
143
172
|
if (jestNeeded) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
173
|
+
try {
|
|
174
|
+
const { stdout, stderr } = await exec("npm ls -g");
|
|
175
|
+
// Solo considerar errores reales, no warnings
|
|
176
|
+
if (stderr && stderr.includes("npm ERR!")) {
|
|
177
|
+
console_1.default.error(`Error executing npm ls -g. Use debug for more info`);
|
|
178
|
+
console_1.default.debug(stderr);
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
if (includesAll(stdout, jsPluginsDependencies))
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
// Si npm retorna código > 0, exec lanzará una excepción
|
|
186
|
+
// Verificar si es un error real o solo un warning
|
|
187
|
+
const errorMessage = (error === null || error === void 0 ? void 0 : error.stderr) || (error === null || error === void 0 ? void 0 : error.message) || String(error);
|
|
188
|
+
if (errorMessage.includes("npm ERR!")) {
|
|
189
|
+
console_1.default.error(`Error executing npm ls -g. Use debug for more info`);
|
|
190
|
+
console_1.default.debug(errorMessage);
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
// Si no es un error real, continuar (podría ser solo un warning)
|
|
194
|
+
console_1.default.debug("npm ls -g returned non-zero exit code, but may be just a warning:", errorMessage);
|
|
149
195
|
}
|
|
150
|
-
if (includesAll(stdout, jsPluginsDependencies))
|
|
151
|
-
return true;
|
|
152
196
|
console_1.default.error("The jest dependencies are not installed");
|
|
153
197
|
const confirmInstall = await cli_ux_1.cli.confirm("Do you want to install the needed dependencies? (y/n)");
|
|
154
198
|
if (!confirmInstall) {
|
|
@@ -167,14 +211,28 @@ const checkNotInstalledDependencies = async (neededPlugins) => {
|
|
|
167
211
|
console_1.default.error("Error upgrading pip. Please install pip manually, run: pip install --upgrade pip");
|
|
168
212
|
console_1.default.debug(error);
|
|
169
213
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
214
|
+
try {
|
|
215
|
+
const { stdout, stderr } = await exec("pip list");
|
|
216
|
+
// Solo considerar errores reales, no warnings
|
|
217
|
+
if (stderr && stderr.includes("Traceback")) {
|
|
218
|
+
console_1.default.error(`Error executing pip list. Use debug for more info`);
|
|
219
|
+
console_1.default.debug(stderr);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
if (includesAll(stdout, pyPluginsDependencies))
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
// Si pip retorna código > 0, verificar si es un error real
|
|
227
|
+
const errorMessage = (error === null || error === void 0 ? void 0 : error.stderr) || (error === null || error === void 0 ? void 0 : error.message) || String(error);
|
|
228
|
+
if (errorMessage.includes("Traceback")) {
|
|
229
|
+
console_1.default.error(`Error executing pip list. Use debug for more info`);
|
|
230
|
+
console_1.default.debug(errorMessage);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
// Si no es un error real, continuar
|
|
234
|
+
console_1.default.debug("pip list returned non-zero exit code, but may be just a warning:", errorMessage);
|
|
175
235
|
}
|
|
176
|
-
if (includesAll(stdout, pyPluginsDependencies))
|
|
177
|
-
return true;
|
|
178
236
|
console_1.default.error("The pytest dependencies are not installed");
|
|
179
237
|
const confirmInstall = await cli_ux_1.cli.confirm("Do you want to install the needed dependencies? (y/n)");
|
|
180
238
|
if (!confirmInstall) {
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express";
|
|
2
|
+
/**
|
|
3
|
+
* Interface for application errors with additional information
|
|
4
|
+
*/
|
|
5
|
+
export interface AppError extends Error {
|
|
6
|
+
statusCode?: number;
|
|
7
|
+
code?: string;
|
|
8
|
+
isOperational?: boolean;
|
|
9
|
+
details?: any;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Base class for operational errors (expected errors)
|
|
13
|
+
*/
|
|
14
|
+
export declare class OperationalError extends Error implements AppError {
|
|
15
|
+
statusCode: number;
|
|
16
|
+
code: string;
|
|
17
|
+
isOperational: boolean;
|
|
18
|
+
details?: any;
|
|
19
|
+
constructor(message: string, details?: any, statusCode?: number, code?: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Validation error (400)
|
|
23
|
+
*/
|
|
24
|
+
export declare class ValidationError extends OperationalError {
|
|
25
|
+
constructor(message: string, details?: any);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Authentication error (401)
|
|
29
|
+
*/
|
|
30
|
+
export declare class UnauthorizedError extends OperationalError {
|
|
31
|
+
constructor(message: string, details?: any);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Authorization error (403)
|
|
35
|
+
*/
|
|
36
|
+
export declare class ForbiddenError extends OperationalError {
|
|
37
|
+
constructor(message: string, details?: any);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resource not found error (404)
|
|
41
|
+
*/
|
|
42
|
+
export declare class NotFoundError extends OperationalError {
|
|
43
|
+
constructor(message: string, details?: any);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Conflict error (409)
|
|
47
|
+
*/
|
|
48
|
+
export declare class ConflictError extends OperationalError {
|
|
49
|
+
constructor(message: string, details?: any);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Internal server error (500)
|
|
53
|
+
*/
|
|
54
|
+
export declare class InternalServerError extends OperationalError {
|
|
55
|
+
constructor(message: string, details?: any);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Wrapper for async handlers that automatically catches errors
|
|
59
|
+
*
|
|
60
|
+
* Usage:
|
|
61
|
+
* app.get('/route', asyncHandler(async (req, res) => {
|
|
62
|
+
* // your async code here
|
|
63
|
+
* }))
|
|
64
|
+
* @param fn - Async handler function to wrap
|
|
65
|
+
* @returns Express middleware that automatically catches errors
|
|
66
|
+
*/
|
|
67
|
+
export declare const asyncHandler: (fn: (req: Request, res: Response, next: NextFunction) => Promise<any>) => (req: Request, res: Response, next: NextFunction) => void;
|
|
68
|
+
/**
|
|
69
|
+
* Middleware to handle not found routes (404)
|
|
70
|
+
* Must be placed after all routes but before the error handler
|
|
71
|
+
* @param req - Express Request object
|
|
72
|
+
* @param res - Express Response object
|
|
73
|
+
* @param next - Express next function
|
|
74
|
+
* @returns void
|
|
75
|
+
*/
|
|
76
|
+
export declare const notFoundHandler: (req: Request, res: Response, next: NextFunction) => void;
|
|
77
|
+
/**
|
|
78
|
+
* Global error handling middleware
|
|
79
|
+
* Must be the last middleware in the chain
|
|
80
|
+
*
|
|
81
|
+
* Catches all unhandled synchronous and asynchronous errors
|
|
82
|
+
* @param error - The caught error
|
|
83
|
+
* @param req - Express Request object
|
|
84
|
+
* @param res - Express Response object
|
|
85
|
+
* @param next - Express next function
|
|
86
|
+
* @returns void
|
|
87
|
+
*/
|
|
88
|
+
export declare const errorHandler: (error: AppError | Error, req: Request, res: Response, next: NextFunction) => void;
|
|
89
|
+
/**
|
|
90
|
+
* Helper to quickly create operational errors
|
|
91
|
+
* @param message - Error message
|
|
92
|
+
* @param details - Additional error details
|
|
93
|
+
* @param statusCode - HTTP status code
|
|
94
|
+
* @param code - Custom error code
|
|
95
|
+
* @returns New OperationalError instance
|
|
96
|
+
*/
|
|
97
|
+
export declare const createError: (message: string, details?: any, statusCode?: number, code?: string) => OperationalError;
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createError = exports.errorHandler = exports.notFoundHandler = exports.asyncHandler = exports.InternalServerError = exports.ConflictError = exports.NotFoundError = exports.ForbiddenError = exports.UnauthorizedError = exports.ValidationError = exports.OperationalError = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Base class for operational errors (expected errors)
|
|
6
|
+
*/
|
|
7
|
+
class OperationalError extends Error {
|
|
8
|
+
constructor(message, details, statusCode = 500, code = "OPERATIONAL_ERROR") {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = this.constructor.name;
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.isOperational = true;
|
|
14
|
+
this.details = details;
|
|
15
|
+
// Maintains correct stack trace
|
|
16
|
+
Error.captureStackTrace(this, this.constructor);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.OperationalError = OperationalError;
|
|
20
|
+
/**
|
|
21
|
+
* Validation error (400)
|
|
22
|
+
*/
|
|
23
|
+
class ValidationError extends OperationalError {
|
|
24
|
+
constructor(message, details) {
|
|
25
|
+
super(message, details, 400, "VALIDATION_ERROR");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.ValidationError = ValidationError;
|
|
29
|
+
/**
|
|
30
|
+
* Authentication error (401)
|
|
31
|
+
*/
|
|
32
|
+
class UnauthorizedError extends OperationalError {
|
|
33
|
+
constructor(message, details) {
|
|
34
|
+
super(message, details, 401, "UNAUTHORIZED");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.UnauthorizedError = UnauthorizedError;
|
|
38
|
+
/**
|
|
39
|
+
* Authorization error (403)
|
|
40
|
+
*/
|
|
41
|
+
class ForbiddenError extends OperationalError {
|
|
42
|
+
constructor(message, details) {
|
|
43
|
+
super(message, details, 403, "FORBIDDEN");
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.ForbiddenError = ForbiddenError;
|
|
47
|
+
/**
|
|
48
|
+
* Resource not found error (404)
|
|
49
|
+
*/
|
|
50
|
+
class NotFoundError extends OperationalError {
|
|
51
|
+
constructor(message, details) {
|
|
52
|
+
super(message, details, 404, "NOT_FOUND");
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
exports.NotFoundError = NotFoundError;
|
|
56
|
+
/**
|
|
57
|
+
* Conflict error (409)
|
|
58
|
+
*/
|
|
59
|
+
class ConflictError extends OperationalError {
|
|
60
|
+
constructor(message, details) {
|
|
61
|
+
super(message, details, 409, "CONFLICT");
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.ConflictError = ConflictError;
|
|
65
|
+
/**
|
|
66
|
+
* Internal server error (500)
|
|
67
|
+
*/
|
|
68
|
+
class InternalServerError extends OperationalError {
|
|
69
|
+
constructor(message, details) {
|
|
70
|
+
super(message, details, 500, "INTERNAL_SERVER_ERROR");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
exports.InternalServerError = InternalServerError;
|
|
74
|
+
/**
|
|
75
|
+
* Determines if an error is operational (expected) or programming (unexpected)
|
|
76
|
+
* @param error - The error to check
|
|
77
|
+
* @returns true if the error is operational, false otherwise
|
|
78
|
+
*/
|
|
79
|
+
function isOperationalError(error) {
|
|
80
|
+
if (error instanceof OperationalError) {
|
|
81
|
+
return error.isOperational === true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Formats the error for HTTP response
|
|
87
|
+
* @param error - The error to format
|
|
88
|
+
* @param isDevelopment - Whether in development mode
|
|
89
|
+
* @returns Object with the formatted response
|
|
90
|
+
*/
|
|
91
|
+
function formatError(error, isDevelopment = false) {
|
|
92
|
+
const isOperational = isOperationalError(error);
|
|
93
|
+
const appError = error;
|
|
94
|
+
const baseResponse = {
|
|
95
|
+
status: "error",
|
|
96
|
+
message: appError.message || "An unexpected error occurred",
|
|
97
|
+
code: appError.code || "INTERNAL_ERROR",
|
|
98
|
+
};
|
|
99
|
+
// In development, include more details
|
|
100
|
+
if (isDevelopment) {
|
|
101
|
+
baseResponse.stack = appError.stack;
|
|
102
|
+
baseResponse.details = appError.details;
|
|
103
|
+
}
|
|
104
|
+
// If it's an operational error, include details if they exist
|
|
105
|
+
if (isOperational && appError.details) {
|
|
106
|
+
baseResponse.details = appError.details;
|
|
107
|
+
}
|
|
108
|
+
// If not operational, don't expose details in production
|
|
109
|
+
if (!isOperational && !isDevelopment) {
|
|
110
|
+
baseResponse.message = "An unexpected error occurred";
|
|
111
|
+
baseResponse.code = "INTERNAL_ERROR";
|
|
112
|
+
}
|
|
113
|
+
return baseResponse;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Determines the appropriate HTTP status code
|
|
117
|
+
* @param error - The error from which to get the status code
|
|
118
|
+
* @returns The HTTP status code
|
|
119
|
+
*/
|
|
120
|
+
function getStatusCode(error) {
|
|
121
|
+
if (error.statusCode) {
|
|
122
|
+
return error.statusCode;
|
|
123
|
+
}
|
|
124
|
+
// Common Node.js errors
|
|
125
|
+
if (error.name === "ValidationError")
|
|
126
|
+
return 400;
|
|
127
|
+
if (error.name === "UnauthorizedError")
|
|
128
|
+
return 401;
|
|
129
|
+
if (error.name === "ForbiddenError")
|
|
130
|
+
return 403;
|
|
131
|
+
if (error.name === "NotFoundError")
|
|
132
|
+
return 404;
|
|
133
|
+
if (error.name === "ConflictError")
|
|
134
|
+
return 409;
|
|
135
|
+
// Default to server error
|
|
136
|
+
return 500;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Logs the error in a structured format
|
|
140
|
+
* @param error - The error to log
|
|
141
|
+
* @param req - Express Request object
|
|
142
|
+
* @returns void
|
|
143
|
+
*/
|
|
144
|
+
function logError(error, req) {
|
|
145
|
+
const appError = error;
|
|
146
|
+
const isOperational = isOperationalError(error);
|
|
147
|
+
const logData = {
|
|
148
|
+
timestamp: new Date().toISOString(),
|
|
149
|
+
method: req.method,
|
|
150
|
+
url: req.originalUrl,
|
|
151
|
+
statusCode: getStatusCode(error),
|
|
152
|
+
error: {
|
|
153
|
+
name: error.name,
|
|
154
|
+
message: error.message,
|
|
155
|
+
code: appError.code,
|
|
156
|
+
isOperational,
|
|
157
|
+
stack: error.stack,
|
|
158
|
+
details: appError.details,
|
|
159
|
+
},
|
|
160
|
+
ip: req.ip || req.socket.remoteAddress,
|
|
161
|
+
userAgent: req.get("user-agent"),
|
|
162
|
+
};
|
|
163
|
+
// Different log based on error type
|
|
164
|
+
if (isOperational) {
|
|
165
|
+
console.error("⚠️ Operational Error:", JSON.stringify(logData, null, 2));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
console.error("❌ Unexpected Error:", JSON.stringify(logData, null, 2));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Wrapper for async handlers that automatically catches errors
|
|
173
|
+
*
|
|
174
|
+
* Usage:
|
|
175
|
+
* app.get('/route', asyncHandler(async (req, res) => {
|
|
176
|
+
* // your async code here
|
|
177
|
+
* }))
|
|
178
|
+
* @param fn - Async handler function to wrap
|
|
179
|
+
* @returns Express middleware that automatically catches errors
|
|
180
|
+
*/
|
|
181
|
+
const asyncHandler = (fn) => {
|
|
182
|
+
return (req, res, next) => {
|
|
183
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
184
|
+
};
|
|
185
|
+
};
|
|
186
|
+
exports.asyncHandler = asyncHandler;
|
|
187
|
+
/**
|
|
188
|
+
* Middleware to handle not found routes (404)
|
|
189
|
+
* Must be placed after all routes but before the error handler
|
|
190
|
+
* @param req - Express Request object
|
|
191
|
+
* @param res - Express Response object
|
|
192
|
+
* @param next - Express next function
|
|
193
|
+
* @returns void
|
|
194
|
+
*/
|
|
195
|
+
const notFoundHandler = (req, res, next) => {
|
|
196
|
+
const error = new NotFoundError(`Route ${req.method} ${req.originalUrl} not found`);
|
|
197
|
+
next(error);
|
|
198
|
+
};
|
|
199
|
+
exports.notFoundHandler = notFoundHandler;
|
|
200
|
+
/**
|
|
201
|
+
* Global error handling middleware
|
|
202
|
+
* Must be the last middleware in the chain
|
|
203
|
+
*
|
|
204
|
+
* Catches all unhandled synchronous and asynchronous errors
|
|
205
|
+
* @param error - The caught error
|
|
206
|
+
* @param req - Express Request object
|
|
207
|
+
* @param res - Express Response object
|
|
208
|
+
* @param next - Express next function
|
|
209
|
+
* @returns void
|
|
210
|
+
*/
|
|
211
|
+
const errorHandler = (error, req, res, next) => {
|
|
212
|
+
// If response already sent, delegate to Express default handler
|
|
213
|
+
if (res.headersSent) {
|
|
214
|
+
return next(error);
|
|
215
|
+
}
|
|
216
|
+
// Determine if we're in development mode
|
|
217
|
+
const isDevelopment = process.env.NODE_ENV === "development" || process.env.DEBUG === "true";
|
|
218
|
+
// Log the error
|
|
219
|
+
logError(error, req);
|
|
220
|
+
// Get status code
|
|
221
|
+
const statusCode = getStatusCode(error);
|
|
222
|
+
// Format response
|
|
223
|
+
const response = formatError(error, isDevelopment);
|
|
224
|
+
// Send response
|
|
225
|
+
res.status(statusCode).json(response);
|
|
226
|
+
};
|
|
227
|
+
exports.errorHandler = errorHandler;
|
|
228
|
+
/**
|
|
229
|
+
* Helper to quickly create operational errors
|
|
230
|
+
* @param message - Error message
|
|
231
|
+
* @param details - Additional error details
|
|
232
|
+
* @param statusCode - HTTP status code
|
|
233
|
+
* @param code - Custom error code
|
|
234
|
+
* @returns New OperationalError instance
|
|
235
|
+
*/
|
|
236
|
+
const createError = (message, details, statusCode, code) => {
|
|
237
|
+
return new OperationalError(message, details, statusCode || 500, code || "OPERATIONAL_ERROR");
|
|
238
|
+
};
|
|
239
|
+
exports.createError = createError;
|
package/lib/utils/rigoActions.js
CHANGED
|
@@ -327,7 +327,7 @@ const addInteractivity = async (token, inputs, webhookUrl) => {
|
|
|
327
327
|
exports.addInteractivity = addInteractivity;
|
|
328
328
|
const generateCodeChallenge = async (token, inputs, webhookUrl) => {
|
|
329
329
|
try {
|
|
330
|
-
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/
|
|
330
|
+
const response = await axios_1.default.post(`${api_1.RIGOBOT_HOST}/v1/prompting/completion/create-structured-coding-filles/`, {
|
|
331
331
|
inputs,
|
|
332
332
|
include_purpose_objective: false,
|
|
333
333
|
execute_async: !!webhookUrl,
|
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.
|
|
4
|
+
"version": "5.0.322",
|
|
5
5
|
"author": "Alejandro Sanchez @alesanchezr",
|
|
6
6
|
"contributors": [
|
|
7
7
|
{
|