@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.
- package/lib/commands/serve.js +161 -43
- 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.d.ts +2 -0
- package/lib/utils/rigoActions.js +1 -1
- package/package.json +1 -1
- package/src/commands/serve.ts +234 -54
- package/src/ui/_app/app.css +1 -1
- package/src/ui/_app/app.js +431 -434
- 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 +641 -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,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
|
|
192
|
-
|
|
193
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1392
|
-
//
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|