@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/src/ui/app.tar.gz
CHANGED
|
Binary file
|
|
@@ -8,10 +8,10 @@ import { cli } from "cli-ux"
|
|
|
8
8
|
import Console from "./console"
|
|
9
9
|
|
|
10
10
|
type TNeededPlugins = {
|
|
11
|
-
needed: string[]
|
|
12
|
-
notInstalled: string[]
|
|
13
|
-
}
|
|
14
|
-
type TPackageManager = "npm" | "pip"
|
|
11
|
+
needed: string[];
|
|
12
|
+
notInstalled: string[];
|
|
13
|
+
};
|
|
14
|
+
type TPackageManager = "npm" | "pip";
|
|
15
15
|
|
|
16
16
|
export const checkNotInstalledPlugins = async (
|
|
17
17
|
exercises: IExercise[],
|
|
@@ -45,8 +45,7 @@ export const checkNotInstalledPlugins = async (
|
|
|
45
45
|
|
|
46
46
|
for (const f of notReadmeFiles) {
|
|
47
47
|
const ext = f.name.split(".").pop()
|
|
48
|
-
if (!ext)
|
|
49
|
-
continue
|
|
48
|
+
if (!ext) continue
|
|
50
49
|
|
|
51
50
|
if (f.name.includes("test")) {
|
|
52
51
|
!testingExtensions.includes(ext) && testingExtensions.push(ext)
|
|
@@ -116,7 +115,9 @@ continue
|
|
|
116
115
|
Console.info("Run: $ learnpack start")
|
|
117
116
|
command.exit(0)
|
|
118
117
|
} else {
|
|
119
|
-
Console.error(
|
|
118
|
+
Console.error(
|
|
119
|
+
"You need to install the plugins to complete this exercise"
|
|
120
|
+
)
|
|
120
121
|
Console.info(
|
|
121
122
|
"To install the plugins run each of the following commands: "
|
|
122
123
|
)
|
|
@@ -149,15 +150,52 @@ const installDependencies = async (
|
|
|
149
150
|
command = `pip install ${deps.join(" ")}`
|
|
150
151
|
}
|
|
151
152
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
try {
|
|
154
|
+
const { stdout, stderr } = await exec(command)
|
|
155
|
+
if (
|
|
156
|
+
stderr &&
|
|
157
|
+
(stderr.includes("npm ERR!") || stderr.includes("Traceback"))
|
|
158
|
+
) {
|
|
159
|
+
Console.error(`Error executing ${command}.`)
|
|
160
|
+
Console.error(stderr)
|
|
161
|
+
return
|
|
162
|
+
}
|
|
158
163
|
|
|
159
|
-
|
|
160
|
-
|
|
164
|
+
Console.info(`Dependencies ${deps.join(" ")} installed...`)
|
|
165
|
+
return true
|
|
166
|
+
} catch (error) {
|
|
167
|
+
// Si npm/pip retorna código > 0, exec lanzará una excepción
|
|
168
|
+
const execError = error as any
|
|
169
|
+
const stdout = execError.stdout || ""
|
|
170
|
+
const stderr = execError.stderr || ""
|
|
171
|
+
const errorMessage = stderr || execError.message || String(error)
|
|
172
|
+
|
|
173
|
+
// Verificar si es un error real
|
|
174
|
+
if (
|
|
175
|
+
errorMessage.includes("npm ERR!") ||
|
|
176
|
+
errorMessage.includes("Traceback")
|
|
177
|
+
) {
|
|
178
|
+
Console.error(`Error executing ${command}.`)
|
|
179
|
+
Console.error(errorMessage)
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Si no es un error real, podría ser solo un warning
|
|
184
|
+
// Verificar si la instalación fue exitosa a pesar del warning
|
|
185
|
+
if (
|
|
186
|
+
stdout &&
|
|
187
|
+
(stdout.includes("added") || stdout.includes("Successfully installed"))
|
|
188
|
+
) {
|
|
189
|
+
Console.info(`Dependencies ${deps.join(" ")} installed...`)
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Si no hay indicios de éxito, se asume fallo (retorna undefined implícitamente)
|
|
194
|
+
Console.debug(
|
|
195
|
+
`${command} returned non-zero exit code, but may be just a warning:`,
|
|
196
|
+
errorMessage
|
|
197
|
+
)
|
|
198
|
+
}
|
|
161
199
|
}
|
|
162
200
|
|
|
163
201
|
export const checkNotInstalledDependencies = async (
|
|
@@ -165,7 +203,10 @@ export const checkNotInstalledDependencies = async (
|
|
|
165
203
|
) => {
|
|
166
204
|
Console.info("Checking needed dependencies...")
|
|
167
205
|
|
|
168
|
-
const jsPluginsDependencies = [
|
|
206
|
+
const jsPluginsDependencies = [
|
|
207
|
+
"jest@29.7.0",
|
|
208
|
+
"jest-environment-jsdom@29.7.0",
|
|
209
|
+
]
|
|
169
210
|
// pytest up to 6.2.5
|
|
170
211
|
const pyPluginsDependencies = ["pytest", "pytest-testdox", "mock"]
|
|
171
212
|
|
|
@@ -188,15 +229,33 @@ export const checkNotInstalledDependencies = async (
|
|
|
188
229
|
}
|
|
189
230
|
|
|
190
231
|
if (jestNeeded) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
232
|
+
try {
|
|
233
|
+
const { stdout, stderr } = await exec("npm ls -g")
|
|
234
|
+
// Solo considerar errores reales, no warnings
|
|
235
|
+
if (stderr && stderr.includes("npm ERR!")) {
|
|
236
|
+
Console.error(`Error executing npm ls -g. Use debug for more info`)
|
|
237
|
+
Console.debug(stderr)
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (includesAll(stdout, jsPluginsDependencies)) return true
|
|
242
|
+
} catch (error) {
|
|
243
|
+
// Si npm retorna código > 0, exec lanzará una excepción
|
|
244
|
+
// Verificar si es un error real o solo un warning
|
|
245
|
+
const errorMessage =
|
|
246
|
+
(error as any)?.stderr || (error as Error)?.message || String(error)
|
|
247
|
+
if (errorMessage.includes("npm ERR!")) {
|
|
248
|
+
Console.error(`Error executing npm ls -g. Use debug for more info`)
|
|
249
|
+
Console.debug(errorMessage)
|
|
250
|
+
return false
|
|
251
|
+
}
|
|
197
252
|
|
|
198
|
-
|
|
199
|
-
|
|
253
|
+
// Si no es un error real, continuar (podría ser solo un warning)
|
|
254
|
+
Console.debug(
|
|
255
|
+
"npm ls -g returned non-zero exit code, but may be just a warning:",
|
|
256
|
+
errorMessage
|
|
257
|
+
)
|
|
258
|
+
}
|
|
200
259
|
|
|
201
260
|
Console.error("The jest dependencies are not installed")
|
|
202
261
|
const confirmInstall = await cli.confirm(
|
|
@@ -231,15 +290,32 @@ return true
|
|
|
231
290
|
Console.debug(error)
|
|
232
291
|
}
|
|
233
292
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
293
|
+
try {
|
|
294
|
+
const { stdout, stderr } = await exec("pip list")
|
|
295
|
+
// Solo considerar errores reales, no warnings
|
|
296
|
+
if (stderr && stderr.includes("Traceback")) {
|
|
297
|
+
Console.error(`Error executing pip list. Use debug for more info`)
|
|
298
|
+
Console.debug(stderr)
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (includesAll(stdout, pyPluginsDependencies)) return true
|
|
303
|
+
} catch (error) {
|
|
304
|
+
// Si pip retorna código > 0, verificar si es un error real
|
|
305
|
+
const errorMessage =
|
|
306
|
+
(error as any)?.stderr || (error as Error)?.message || String(error)
|
|
307
|
+
if (errorMessage.includes("Traceback")) {
|
|
308
|
+
Console.error(`Error executing pip list. Use debug for more info`)
|
|
309
|
+
Console.debug(errorMessage)
|
|
310
|
+
return false
|
|
311
|
+
}
|
|
240
312
|
|
|
241
|
-
|
|
242
|
-
|
|
313
|
+
// Si no es un error real, continuar
|
|
314
|
+
Console.debug(
|
|
315
|
+
"pip list returned non-zero exit code, but may be just a warning:",
|
|
316
|
+
errorMessage
|
|
317
|
+
)
|
|
318
|
+
}
|
|
243
319
|
|
|
244
320
|
Console.error("The pytest dependencies are not installed")
|
|
245
321
|
const confirmInstall = await cli.confirm(
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { Request, Response, NextFunction } from "express"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Interface for application errors with additional information
|
|
5
|
+
*/
|
|
6
|
+
export interface AppError extends Error {
|
|
7
|
+
statusCode?: number;
|
|
8
|
+
code?: string;
|
|
9
|
+
isOperational?: boolean;
|
|
10
|
+
details?: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base class for operational errors (expected errors)
|
|
15
|
+
*/
|
|
16
|
+
export class OperationalError extends Error implements AppError {
|
|
17
|
+
statusCode: number
|
|
18
|
+
code: string
|
|
19
|
+
isOperational: boolean
|
|
20
|
+
details?: any
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
message: string,
|
|
24
|
+
details?: any,
|
|
25
|
+
statusCode = 500,
|
|
26
|
+
code = "OPERATIONAL_ERROR"
|
|
27
|
+
) {
|
|
28
|
+
super(message)
|
|
29
|
+
this.name = this.constructor.name
|
|
30
|
+
this.statusCode = statusCode
|
|
31
|
+
this.code = code
|
|
32
|
+
this.isOperational = true
|
|
33
|
+
this.details = details
|
|
34
|
+
|
|
35
|
+
// Maintains correct stack trace
|
|
36
|
+
Error.captureStackTrace(this, this.constructor)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validation error (400)
|
|
42
|
+
*/
|
|
43
|
+
export class ValidationError extends OperationalError {
|
|
44
|
+
constructor(message: string, details?: any) {
|
|
45
|
+
super(message, details, 400, "VALIDATION_ERROR")
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Authentication error (401)
|
|
51
|
+
*/
|
|
52
|
+
export class UnauthorizedError extends OperationalError {
|
|
53
|
+
constructor(message: string, details?: any) {
|
|
54
|
+
super(message, details, 401, "UNAUTHORIZED")
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Authorization error (403)
|
|
60
|
+
*/
|
|
61
|
+
export class ForbiddenError extends OperationalError {
|
|
62
|
+
constructor(message: string, details?: any) {
|
|
63
|
+
super(message, details, 403, "FORBIDDEN")
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resource not found error (404)
|
|
69
|
+
*/
|
|
70
|
+
export class NotFoundError extends OperationalError {
|
|
71
|
+
constructor(message: string, details?: any) {
|
|
72
|
+
super(message, details, 404, "NOT_FOUND")
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Conflict error (409)
|
|
78
|
+
*/
|
|
79
|
+
export class ConflictError extends OperationalError {
|
|
80
|
+
constructor(message: string, details?: any) {
|
|
81
|
+
super(message, details, 409, "CONFLICT")
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Internal server error (500)
|
|
87
|
+
*/
|
|
88
|
+
export class InternalServerError extends OperationalError {
|
|
89
|
+
constructor(message: string, details?: any) {
|
|
90
|
+
super(message, details, 500, "INTERNAL_SERVER_ERROR")
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Determines if an error is operational (expected) or programming (unexpected)
|
|
96
|
+
* @param error - The error to check
|
|
97
|
+
* @returns true if the error is operational, false otherwise
|
|
98
|
+
*/
|
|
99
|
+
function isOperationalError(error: any): boolean {
|
|
100
|
+
if (error instanceof OperationalError) {
|
|
101
|
+
return error.isOperational === true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return false
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Formats the error for HTTP response
|
|
109
|
+
* @param error - The error to format
|
|
110
|
+
* @param isDevelopment - Whether in development mode
|
|
111
|
+
* @returns Object with the formatted response
|
|
112
|
+
*/
|
|
113
|
+
function formatError(error: AppError | Error, isDevelopment = false) {
|
|
114
|
+
const isOperational = isOperationalError(error)
|
|
115
|
+
const appError = error as AppError
|
|
116
|
+
|
|
117
|
+
const baseResponse: any = {
|
|
118
|
+
status: "error",
|
|
119
|
+
message: appError.message || "An unexpected error occurred",
|
|
120
|
+
code: appError.code || "INTERNAL_ERROR",
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// In development, include more details
|
|
124
|
+
if (isDevelopment) {
|
|
125
|
+
baseResponse.stack = appError.stack
|
|
126
|
+
baseResponse.details = appError.details
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// If it's an operational error, include details if they exist
|
|
130
|
+
if (isOperational && appError.details) {
|
|
131
|
+
baseResponse.details = appError.details
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// If not operational, don't expose details in production
|
|
135
|
+
if (!isOperational && !isDevelopment) {
|
|
136
|
+
baseResponse.message = "An unexpected error occurred"
|
|
137
|
+
baseResponse.code = "INTERNAL_ERROR"
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return baseResponse
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Determines the appropriate HTTP status code
|
|
145
|
+
* @param error - The error from which to get the status code
|
|
146
|
+
* @returns The HTTP status code
|
|
147
|
+
*/
|
|
148
|
+
function getStatusCode(error: AppError | Error): number {
|
|
149
|
+
if ((error as AppError).statusCode) {
|
|
150
|
+
return (error as AppError).statusCode!
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Common Node.js errors
|
|
154
|
+
if (error.name === "ValidationError") return 400
|
|
155
|
+
if (error.name === "UnauthorizedError") return 401
|
|
156
|
+
if (error.name === "ForbiddenError") return 403
|
|
157
|
+
if (error.name === "NotFoundError") return 404
|
|
158
|
+
if (error.name === "ConflictError") return 409
|
|
159
|
+
|
|
160
|
+
// Default to server error
|
|
161
|
+
return 500
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Logs the error in a structured format
|
|
166
|
+
* @param error - The error to log
|
|
167
|
+
* @param req - Express Request object
|
|
168
|
+
* @returns void
|
|
169
|
+
*/
|
|
170
|
+
function logError(error: AppError | Error, req: Request): void {
|
|
171
|
+
const appError = error as AppError
|
|
172
|
+
const isOperational = isOperationalError(error)
|
|
173
|
+
|
|
174
|
+
const logData = {
|
|
175
|
+
timestamp: new Date().toISOString(),
|
|
176
|
+
method: req.method,
|
|
177
|
+
url: req.originalUrl,
|
|
178
|
+
statusCode: getStatusCode(error),
|
|
179
|
+
error: {
|
|
180
|
+
name: error.name,
|
|
181
|
+
message: error.message,
|
|
182
|
+
code: appError.code,
|
|
183
|
+
isOperational,
|
|
184
|
+
stack: error.stack,
|
|
185
|
+
details: appError.details,
|
|
186
|
+
},
|
|
187
|
+
ip: req.ip || req.socket.remoteAddress,
|
|
188
|
+
userAgent: req.get("user-agent"),
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Different log based on error type
|
|
192
|
+
if (isOperational) {
|
|
193
|
+
console.error("⚠️ Operational Error:", JSON.stringify(logData, null, 2))
|
|
194
|
+
} else {
|
|
195
|
+
console.error("❌ Unexpected Error:", JSON.stringify(logData, null, 2))
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Wrapper for async handlers that automatically catches errors
|
|
201
|
+
*
|
|
202
|
+
* Usage:
|
|
203
|
+
* app.get('/route', asyncHandler(async (req, res) => {
|
|
204
|
+
* // your async code here
|
|
205
|
+
* }))
|
|
206
|
+
* @param fn - Async handler function to wrap
|
|
207
|
+
* @returns Express middleware that automatically catches errors
|
|
208
|
+
*/
|
|
209
|
+
export const asyncHandler = (
|
|
210
|
+
fn: (req: Request, res: Response, next: NextFunction) => Promise<any>
|
|
211
|
+
) => {
|
|
212
|
+
return (req: Request, res: Response, next: NextFunction) => {
|
|
213
|
+
Promise.resolve(fn(req, res, next)).catch(next)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Middleware to handle not found routes (404)
|
|
219
|
+
* Must be placed after all routes but before the error handler
|
|
220
|
+
* @param req - Express Request object
|
|
221
|
+
* @param res - Express Response object
|
|
222
|
+
* @param next - Express next function
|
|
223
|
+
* @returns void
|
|
224
|
+
*/
|
|
225
|
+
export const notFoundHandler = (
|
|
226
|
+
req: Request,
|
|
227
|
+
res: Response,
|
|
228
|
+
next: NextFunction
|
|
229
|
+
): void => {
|
|
230
|
+
const error = new NotFoundError(
|
|
231
|
+
`Route ${req.method} ${req.originalUrl} not found`
|
|
232
|
+
)
|
|
233
|
+
next(error)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Global error handling middleware
|
|
238
|
+
* Must be the last middleware in the chain
|
|
239
|
+
*
|
|
240
|
+
* Catches all unhandled synchronous and asynchronous errors
|
|
241
|
+
* @param error - The caught error
|
|
242
|
+
* @param req - Express Request object
|
|
243
|
+
* @param res - Express Response object
|
|
244
|
+
* @param next - Express next function
|
|
245
|
+
* @returns void
|
|
246
|
+
*/
|
|
247
|
+
export const errorHandler = (
|
|
248
|
+
error: AppError | Error,
|
|
249
|
+
req: Request,
|
|
250
|
+
res: Response,
|
|
251
|
+
next: NextFunction
|
|
252
|
+
): void => {
|
|
253
|
+
// If response already sent, delegate to Express default handler
|
|
254
|
+
if (res.headersSent) {
|
|
255
|
+
return next(error)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Determine if we're in development mode
|
|
259
|
+
const isDevelopment =
|
|
260
|
+
process.env.NODE_ENV === "development" || process.env.DEBUG === "true"
|
|
261
|
+
|
|
262
|
+
// Log the error
|
|
263
|
+
logError(error, req)
|
|
264
|
+
|
|
265
|
+
// Get status code
|
|
266
|
+
const statusCode = getStatusCode(error)
|
|
267
|
+
|
|
268
|
+
// Format response
|
|
269
|
+
const response = formatError(error, isDevelopment)
|
|
270
|
+
|
|
271
|
+
// Send response
|
|
272
|
+
res.status(statusCode).json(response)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Helper to quickly create operational errors
|
|
277
|
+
* @param message - Error message
|
|
278
|
+
* @param details - Additional error details
|
|
279
|
+
* @param statusCode - HTTP status code
|
|
280
|
+
* @param code - Custom error code
|
|
281
|
+
* @returns New OperationalError instance
|
|
282
|
+
*/
|
|
283
|
+
export const createError = (
|
|
284
|
+
message: string,
|
|
285
|
+
details?: any,
|
|
286
|
+
statusCode?: number,
|
|
287
|
+
code?: string
|
|
288
|
+
): OperationalError => {
|
|
289
|
+
return new OperationalError(
|
|
290
|
+
message,
|
|
291
|
+
details,
|
|
292
|
+
statusCode || 500,
|
|
293
|
+
code || "OPERATIONAL_ERROR"
|
|
294
|
+
)
|
|
295
|
+
}
|