@learnpack/learnpack 5.0.320 → 5.0.323

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.
@@ -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,15 +183,36 @@ 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
+ console.log("EXERCISE", exercise);
191
+ if (!exercise.id || !exercise.title) {
192
+ throw new errorHandler_1.ValidationError(`Exercise is missing required properties: id=${exercise.id}, title=${exercise.title}`);
193
+ }
185
194
  const exSlug = (0, creatorUtilities_2.slugify)(exercise.id + "-" + exercise.title);
186
195
  console.log("Starting initial content generation for", exSlug);
187
196
  const webhookUrl = `${process.env.HOST}/webhooks/${courseSlug}/initial-content-processor/${exercise.uid}/${rigoToken}`;
188
- // Determine if this is the first lesson
189
197
  const exerciseIndex = steps.findIndex(lesson => lesson.uid === exercise.uid);
190
198
  const isFirstLesson = exerciseIndex === 0;
191
- const endpointSlug = isFirstLesson ?
192
- "initial-step-content-generator" :
193
- "generate-step-initial-content";
199
+ const isLastLesson = exerciseIndex === steps.length - 1;
200
+ let endpointSlug;
201
+ let passTopicDescription = false;
202
+ if (isFirstLesson) {
203
+ endpointSlug = "generate-learnpack-intro-content";
204
+ }
205
+ else if (isLastLesson) {
206
+ endpointSlug = "generate-learnpack-outro-content";
207
+ }
208
+ else if (exercise.id.endsWith(".0")) {
209
+ passTopicDescription = true;
210
+ endpointSlug = "generate-learnpack-topic-intro-content";
211
+ }
212
+ else {
213
+ passTopicDescription = true;
214
+ endpointSlug = "generate-learnpack-subtopic-content";
215
+ }
194
216
  // Emit notification that initial content generation is starting
195
217
  (0, creatorSocket_1.emitToCourse)(courseSlug, "course-creation", {
196
218
  lesson: exSlug,
@@ -204,12 +226,16 @@ async function startInitialContentGeneration(rigoToken, steps, packageContext, e
204
226
  // Add random 6-digit number to avoid cache issues
205
227
  // eslint-disable-next-line no-mixed-operators
206
228
  const randomCacheEvict = Math.floor(100000 + Math.random() * 900000);
207
- const res = await (0, rigoActions_1.initialContentGenerator)(rigoToken.trim(), {
208
- // prev_lesson: lastLesson,
229
+ const inputs = {
209
230
  output_language: packageContext.language || "en",
210
231
  current_syllabus: JSON.stringify(fullSyllabus),
211
232
  lesson_description: JSON.stringify(lessonCleaner(exercise)) + `-${randomCacheEvict}`,
212
- }, webhookUrl, endpointSlug);
233
+ target_word_count: String(PARAMS.max_words),
234
+ topic_description: passTopicDescription ?
235
+ String(exercise.description) :
236
+ undefined,
237
+ };
238
+ const res = await (0, rigoActions_1.initialContentGenerator)(rigoToken.trim(), inputs, webhookUrl, endpointSlug);
213
239
  console.log("INITIAL CONTENT GENERATOR RES", res);
214
240
  return res.id;
215
241
  }
@@ -649,9 +675,8 @@ class ServeCommand extends SessionCommand_1.default {
649
675
  return res.status(404).json({ error: "Exercise not found" });
650
676
  }
651
677
  const exerciseDir = `courses/${courseSlug}/exercises/${exercise.slug}`;
652
- for (const fileStr of files) {
678
+ for (const fileObj of files) {
653
679
  try {
654
- const fileObj = JSON.parse(fileStr);
655
680
  console.log(`📄 Processing file: ${fileObj.name}`);
656
681
  // Save the main file with content
657
682
  if (fileObj.name && fileObj.content) {
@@ -696,7 +721,7 @@ class ServeCommand extends SessionCommand_1.default {
696
721
  res.status(500).json({ error: error.message });
697
722
  }
698
723
  });
699
- app.post("/actions/continue-generating/:courseSlug/:lessonUid", async (req, res) => {
724
+ app.post("/actions/continue-generating/:courseSlug/:lessonUid", (0, errorHandler_1.asyncHandler)(async (req, res) => {
700
725
  const { courseSlug, lessonUid } = req.params;
701
726
  const { feedback, mode } = req.body;
702
727
  const rigoToken = req.header("x-rigo-token");
@@ -706,13 +731,23 @@ class ServeCommand extends SessionCommand_1.default {
706
731
  // console.log("FEEDBACK", feedback);
707
732
  // console.log("MODE", mode);
708
733
  if (!rigoToken) {
709
- return res.status(400).json({
710
- error: "Rigo token is required. x-rigo-token header is missing",
711
- });
734
+ throw new errorHandler_1.ValidationError("Rigo token is required. x-rigo-token header is missing");
712
735
  }
713
736
  const syllabus = await getSyllabus(courseSlug, bucket);
714
737
  const exercise = syllabus.lessons.find(lesson => lesson.uid === lessonUid);
715
738
  const exerciseIndex = syllabus.lessons.findIndex(lesson => lesson.uid === lessonUid);
739
+ // Validate that exercise exists
740
+ if (!exercise) {
741
+ throw new errorHandler_1.NotFoundError(`Lesson with UID "${lessonUid}" not found in course "${courseSlug}"`);
742
+ }
743
+ // Validate that exercise index is valid (defensive check)
744
+ if (exerciseIndex === -1) {
745
+ throw new errorHandler_1.NotFoundError(`Lesson with UID "${lessonUid}" not found in course "${courseSlug}"`);
746
+ }
747
+ // Validate that exercise has required properties
748
+ if (!exercise.id || !exercise.title) {
749
+ throw new errorHandler_1.ValidationError(`Lesson "${lessonUid}" is missing required properties (id or title)`);
750
+ }
716
751
  // previous exercise
717
752
  let previousReadme = "---";
718
753
  const previousExercise = syllabus.lessons[exerciseIndex - 1];
@@ -753,7 +788,7 @@ class ServeCommand extends SessionCommand_1.default {
753
788
  syllabus.generationMode = mode;
754
789
  await saveSyllabus(courseSlug, syllabus, bucket);
755
790
  res.json({ status: "SUCCESS" });
756
- });
791
+ }));
757
792
  // TODO: Check if this command is being used
758
793
  app.post("/actions/generate-image/:courseSlug", async (req, res) => {
759
794
  const rigoToken = req.header("x-rigo-token");
@@ -1255,6 +1290,12 @@ class ServeCommand extends SessionCommand_1.default {
1255
1290
  console.log("RECEIVING TRANSLATION WEBHOOK", body);
1256
1291
  const readmePath = `courses/${courseSlug}/exercises/${exSlug}/README${(0, creatorUtilities_1.getReadmeExtension)(body.parsed.output_language_code)}`;
1257
1292
  await uploadFileToBucket(bucket, body.parsed.translation, readmePath);
1293
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-completed", {
1294
+ exercise: exSlug,
1295
+ language: body.parsed.output_language_code,
1296
+ status: "completed",
1297
+ message: `Translation completed for ${exSlug} to ${body.parsed.output_language_code}`,
1298
+ });
1258
1299
  res.json({ status: "SUCCESS" });
1259
1300
  });
1260
1301
  app.get("/check-preview-image/:slug", async (req, res) => {
@@ -1383,22 +1424,49 @@ class ServeCommand extends SessionCommand_1.default {
1383
1424
  res.set("Content-Disposition", `attachment; filename="${file}"`);
1384
1425
  fileStream.pipe(res);
1385
1426
  });
1386
- app.put("/exercise/:slug/file/:fileName", express.text(), async (req, res) => {
1427
+ app.put("/exercise/:slug/file/:fileName", express.text(), (0, errorHandler_1.asyncHandler)(async (req, res) => {
1428
+ var _a, _b;
1387
1429
  const { slug, fileName } = req.params;
1388
1430
  const query = req.query;
1389
- console.log(`PUT /exercise/${slug}/file/${fileName}`);
1390
1431
  const courseSlug = query.slug;
1391
- // console.log("COURSE SLUG", courseSlug)
1392
- // Update the file in the bucket
1393
- const file = await bucket.file(`courses/${courseSlug}/exercises/${slug}/${fileName}`);
1394
- await file.save(req.body);
1395
- const created = await file.exists();
1396
- // console.log("File updated", created)
1397
- res.send({
1398
- message: "File updated",
1399
- created,
1400
- });
1401
- });
1432
+ console.log(`PUT /exercise/${slug}/file/${fileName}`);
1433
+ // Validate required parameters
1434
+ if (!courseSlug) {
1435
+ throw new errorHandler_1.ValidationError("Course slug is required in query parameters");
1436
+ }
1437
+ if (!fileName || !slug) {
1438
+ throw new errorHandler_1.ValidationError("File name and exercise slug are required");
1439
+ }
1440
+ try {
1441
+ // Update the file in the bucket
1442
+ const file = bucket.file(`courses/${courseSlug}/exercises/${slug}/${fileName}`);
1443
+ await file.save(req.body, {
1444
+ resumable: false,
1445
+ });
1446
+ const created = await file.exists();
1447
+ res.send({
1448
+ message: "File updated",
1449
+ created,
1450
+ });
1451
+ }
1452
+ catch (error) {
1453
+ // Handle Google Cloud Storage rate limit errors (429)
1454
+ if (error.code === 429 ||
1455
+ ((_a = error.message) === null || _a === void 0 ? void 0 : _a.includes("rate limit")) ||
1456
+ ((_b = error.message) === null || _b === void 0 ? void 0 : _b.includes("rateLimitExceeded"))) {
1457
+ throw new errorHandler_1.ConflictError("Storage rate limit exceeded. Please try again in a few moments.", {
1458
+ code: "STORAGE_RATE_LIMIT",
1459
+ retryAfter: 60, // seconds
1460
+ });
1461
+ }
1462
+ // Handle other GCS errors
1463
+ if (error.code) {
1464
+ throw new errorHandler_1.InternalServerError(`Storage error: ${error.message || "Failed to update file"}`, { code: error.code });
1465
+ }
1466
+ // Re-throw if it's already an operational error
1467
+ throw error;
1468
+ }
1469
+ }));
1402
1470
  // Create a new step for a course
1403
1471
  app.post("/course/:slug/create-step", async (req, res) => {
1404
1472
  console.log("POST /course/:slug/create-step");
@@ -1469,35 +1537,40 @@ class ServeCommand extends SessionCommand_1.default {
1469
1537
  }
1470
1538
  res.send({ message: "Files renamed" });
1471
1539
  });
1472
- app.post("/actions/translate", express.json(), async (req, res) => {
1473
- console.log("POST /actions/translate");
1474
- const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
1475
- const query = req.query;
1476
- const courseSlug = query.slug;
1477
- if (!rigoToken) {
1478
- return res.status(400).json({ error: "RigoToken not found" });
1479
- }
1480
- const languagesToTranslate = languages.split(",");
1481
- const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
1482
- raw_languages: languagesToTranslate.join(","),
1483
- });
1484
- const languageCodes = languageCodesRes.parsed.language_codes;
1540
+ async function processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket) {
1485
1541
  try {
1542
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-started", {
1543
+ languages: languageCodes,
1544
+ exercises: exerciseSlugs,
1545
+ status: "started",
1546
+ message: "Translation process started",
1547
+ });
1486
1548
  await Promise.all(exerciseSlugs.map(async (slug) => {
1487
1549
  const readmePath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(currentLanguage)}`;
1488
1550
  const readme = await bucket.file(readmePath).download();
1489
1551
  await Promise.all(languageCodes.map(async (language) => {
1490
- // verify if the translation already exists
1491
1552
  const translationPath = `courses/${courseSlug}/exercises/${slug}/README${(0, creatorUtilities_1.getReadmeExtension)(language)}`;
1492
1553
  const [exists] = await bucket.file(translationPath).exists();
1493
1554
  if (exists) {
1494
1555
  console.log(`Translation in ${language} already exists for exercise ${slug}`);
1556
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1557
+ exercise: slug,
1558
+ language: language,
1559
+ status: "skipped",
1560
+ message: `Translation in ${language} already exists`,
1561
+ });
1495
1562
  return;
1496
1563
  }
1497
1564
  await (0, rigoActions_1.translateExercise)(rigoToken, {
1498
1565
  text_to_translate: readme.toString(),
1499
1566
  output_language: language,
1500
1567
  }, `${process.env.HOST}/webhooks/${courseSlug}/${slug}/save-translation`);
1568
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-progress", {
1569
+ exercise: slug,
1570
+ language: language,
1571
+ status: "initiated",
1572
+ message: `Translation job initiated for ${slug} to ${language}`,
1573
+ });
1501
1574
  }));
1502
1575
  }));
1503
1576
  const course = await bucket
@@ -1527,7 +1600,6 @@ class ServeCommand extends SessionCommand_1.default {
1527
1600
  await uploadFileToBucket(bucket, JSON.stringify(courseJson), `courses/${courseSlug}/learn.json`);
1528
1601
  currentLanguages = Object.keys(courseJson.title);
1529
1602
  }
1530
- // Check that all the READMEs exists
1531
1603
  const missingReadmeTranslations = [];
1532
1604
  let firstAvailable = "";
1533
1605
  for (const languageCode of currentLanguages) {
@@ -1551,7 +1623,49 @@ class ServeCommand extends SessionCommand_1.default {
1551
1623
  output_language: languageCode,
1552
1624
  }, `${process.env.HOST}/webhooks/${courseSlug}/initial-readme-processor`);
1553
1625
  }));
1554
- return res.status(200).json({ message: "Translated exercises" });
1626
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-initiated", {
1627
+ status: "completed",
1628
+ message: "All translation jobs have been initiated",
1629
+ languages: languageCodes,
1630
+ exercises: exerciseSlugs,
1631
+ });
1632
+ }
1633
+ catch (error) {
1634
+ console.error("Error processing translations:", error);
1635
+ (0, creatorSocket_1.emitToCourse)(courseSlug, "translation-error", {
1636
+ status: "error",
1637
+ error: error.message,
1638
+ message: "Error occurred during translation processing",
1639
+ });
1640
+ throw error;
1641
+ }
1642
+ }
1643
+ app.post("/actions/translate", express.json(), async (req, res) => {
1644
+ console.log("POST /actions/translate");
1645
+ const { exerciseSlugs, languages, rigoToken, currentLanguage } = req.body;
1646
+ const query = req.query;
1647
+ const courseSlug = typeof query.slug === "string" ? query.slug : undefined;
1648
+ if (!rigoToken) {
1649
+ return res.status(400).json({ error: "RigoToken not found" });
1650
+ }
1651
+ if (!courseSlug) {
1652
+ return res.status(400).json({ error: "Course slug not found" });
1653
+ }
1654
+ const languagesToTranslate = languages.split(",");
1655
+ try {
1656
+ const languageCodesRes = await (0, rigoActions_1.getLanguageCodes)(rigoToken, {
1657
+ raw_languages: languagesToTranslate.join(","),
1658
+ });
1659
+ const languageCodes = languageCodesRes.parsed.language_codes;
1660
+ res.status(200).json({
1661
+ message: "Translation started",
1662
+ languages: languageCodes,
1663
+ exercises: exerciseSlugs,
1664
+ status: "processing",
1665
+ });
1666
+ processTranslationsAsync(courseSlug, exerciseSlugs, languageCodes, rigoToken, currentLanguage, bucket).catch(error => {
1667
+ console.error("Error in background translation processing:", error);
1668
+ });
1555
1669
  }
1556
1670
  catch (error) {
1557
1671
  console.log(error, "ERROR");
@@ -2393,6 +2507,10 @@ class ServeCommand extends SessionCommand_1.default {
2393
2507
  .json({ error: "Export failed", details: error.message });
2394
2508
  }
2395
2509
  });
2510
+ // 404 error handler
2511
+ app.use(errorHandler_1.notFoundHandler);
2512
+ // Global error handler
2513
+ app.use(errorHandler_1.errorHandler);
2396
2514
  server.listen(PORT, () => {
2397
2515
  console.log(`🚀 Creator UI server running at http://localhost:${PORT}/creator`);
2398
2516
  });
@@ -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 | undefined>;
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
- const { stdout, stderr } = await exec(command);
118
- if (stderr && (stderr.includes("npm ERR!") || stderr.includes("Traceback"))) {
119
- console_1.default.error(`Error executing ${command}.`);
120
- console_1.default.error(stderr);
121
- return;
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 = ["jest@29.7.0", "jest-environment-jsdom@29.7.0"];
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
- const { stdout, stderr } = await exec("npm ls -g");
145
- if (stderr) {
146
- console_1.default.error(`Error executing ${npmLsCommand}. Use debug for more info`);
147
- console_1.default.debug(stderr);
148
- return false;
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
- const { stdout, stderr } = await exec("pip list");
171
- if (stderr) {
172
- console_1.default.error(`Error executing pip list. Use debug for more info`);
173
- console_1.default.debug(stderr);
174
- return;
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;