@stacksjs/error-handling 0.64.6 → 0.66.0
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/README.md +4 -4
- package/dist/handler.d.ts +16 -0
- package/dist/http.d.ts +4 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +122 -2
- package/dist/index.js.map +86 -43
- package/dist/utils.d.ts +4 -0
- package/package.json +13 -25
- package/src/handler.ts +111 -28
- package/src/http.ts +6 -0
- package/src/index.ts +4 -2
- package/src/utils.ts +26 -0
- package/src/views/500.html +37 -0
package/dist/utils.d.ts
ADDED
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/error-handling",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.66.0",
|
|
5
5
|
"description": "Type safe error handling.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
|
+
"contributors": ["Chris Breuer <chris@stacksjs.org>"],
|
|
7
8
|
"license": "MIT",
|
|
8
9
|
"funding": "https://github.com/sponsors/chrisbbreuer",
|
|
9
10
|
"homepage": "https://github.com/stacksjs/stacks/tree/main/storage/framework/core/error-handling#readme",
|
|
@@ -15,13 +16,7 @@
|
|
|
15
16
|
"bugs": {
|
|
16
17
|
"url": "https://github.com/stacksjs/stacks/issues"
|
|
17
18
|
},
|
|
18
|
-
"keywords": [
|
|
19
|
-
"errors",
|
|
20
|
-
"error-handling",
|
|
21
|
-
"neverthrow",
|
|
22
|
-
"type safe",
|
|
23
|
-
"stacks"
|
|
24
|
-
],
|
|
19
|
+
"keywords": ["errors", "error-handling", "neverthrow", "type safe", "stacks"],
|
|
25
20
|
"exports": {
|
|
26
21
|
".": {
|
|
27
22
|
"bun": "./src/index.ts",
|
|
@@ -33,26 +28,19 @@
|
|
|
33
28
|
},
|
|
34
29
|
"module": "dist/index.js",
|
|
35
30
|
"types": "dist/index.d.ts",
|
|
36
|
-
"
|
|
37
|
-
"Chris Breuer <chris@stacksjs.org>"
|
|
38
|
-
],
|
|
39
|
-
"files": [
|
|
40
|
-
"README.md",
|
|
41
|
-
"dist",
|
|
42
|
-
"src"
|
|
43
|
-
],
|
|
31
|
+
"files": ["README.md", "dist", "src"],
|
|
44
32
|
"scripts": {
|
|
45
|
-
"build": "bun
|
|
46
|
-
"typecheck": "bun
|
|
33
|
+
"build": "bun build.ts",
|
|
34
|
+
"typecheck": "bun tsc --noEmit",
|
|
47
35
|
"prepublishOnly": "bun run build"
|
|
48
36
|
},
|
|
49
|
-
"dependencies": {
|
|
50
|
-
"@stacksjs/cli": "latest",
|
|
51
|
-
"@stacksjs/path": "latest",
|
|
52
|
-
"@stacksjs/types": "latest",
|
|
53
|
-
"neverthrow": "^7.0.1"
|
|
54
|
-
},
|
|
55
37
|
"devDependencies": {
|
|
56
|
-
"@stacksjs/
|
|
38
|
+
"@stacksjs/cli": "0.65.1",
|
|
39
|
+
"@stacksjs/config": "0.65.1",
|
|
40
|
+
"@stacksjs/development": "0.65.1",
|
|
41
|
+
"@stacksjs/path": "0.65.1",
|
|
42
|
+
"@stacksjs/types": "0.65.1",
|
|
43
|
+
"@stacksjs/validation": "0.65.1",
|
|
44
|
+
"neverthrow": "^8.0.0"
|
|
57
45
|
}
|
|
58
46
|
}
|
package/src/handler.ts
CHANGED
|
@@ -1,25 +1,51 @@
|
|
|
1
|
+
import type { ErrorOptions } from '@stacksjs/logging'
|
|
2
|
+
import { access, appendFile, mkdir } from 'node:fs/promises'
|
|
3
|
+
import { dirname } from 'node:path'
|
|
4
|
+
import process from 'node:process'
|
|
5
|
+
import { italic, stripAnsi } from '@stacksjs/cli'
|
|
6
|
+
import { config } from '@stacksjs/config'
|
|
1
7
|
import * as path from '@stacksjs/path'
|
|
2
8
|
import { ExitCode } from '@stacksjs/types'
|
|
3
|
-
import
|
|
9
|
+
import { isString } from '@stacksjs/validation'
|
|
4
10
|
|
|
5
|
-
|
|
6
|
-
silent?: boolean
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export const StacksError = Error
|
|
11
|
+
type ErrorMessage = string
|
|
10
12
|
|
|
11
13
|
export class ErrorHandler {
|
|
12
|
-
|
|
14
|
+
static isTestEnvironment = false
|
|
15
|
+
static shouldExitProcess = true
|
|
16
|
+
|
|
17
|
+
static handle(err: Error | ErrorMessage | unknown, options?: ErrorOptions): Error {
|
|
18
|
+
this.shouldExitProcess = options?.shouldExit !== false
|
|
19
|
+
if (options?.silent !== true)
|
|
20
|
+
this.writeErrorToConsole(err)
|
|
21
|
+
|
|
22
|
+
let errorMessage: string
|
|
23
|
+
|
|
24
|
+
if (options?.message) {
|
|
25
|
+
// Use the message from options if provided
|
|
26
|
+
errorMessage = options.message
|
|
27
|
+
}
|
|
28
|
+
else if (err instanceof Error) {
|
|
29
|
+
errorMessage = err.message
|
|
30
|
+
}
|
|
31
|
+
else if (typeof err === 'string') {
|
|
32
|
+
errorMessage = err
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
errorMessage = JSON.stringify(err)
|
|
36
|
+
}
|
|
13
37
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (options?.silent !== false) this.writeErrorToConsole(err)
|
|
38
|
+
// Create a new Error with the determined message
|
|
39
|
+
const error = new Error(errorMessage)
|
|
17
40
|
|
|
18
|
-
|
|
41
|
+
// If the original err was an Error instance, copy its properties
|
|
42
|
+
if (err instanceof Error) {
|
|
43
|
+
Object.assign(error, err)
|
|
44
|
+
}
|
|
19
45
|
|
|
20
|
-
this.writeErrorToFile(
|
|
46
|
+
this.writeErrorToFile(error).catch(e => console.error(e))
|
|
21
47
|
|
|
22
|
-
return
|
|
48
|
+
return error
|
|
23
49
|
}
|
|
24
50
|
|
|
25
51
|
static handleError(err: Error, options?: ErrorOptions): Error {
|
|
@@ -27,7 +53,7 @@ export class ErrorHandler {
|
|
|
27
53
|
return err
|
|
28
54
|
}
|
|
29
55
|
|
|
30
|
-
static async writeErrorToFile(err: Error | unknown) {
|
|
56
|
+
static async writeErrorToFile(err: Error | unknown): Promise<void> {
|
|
31
57
|
if (!(err instanceof Error)) {
|
|
32
58
|
console.error('Error is not an instance of Error:', err)
|
|
33
59
|
return
|
|
@@ -37,29 +63,86 @@ export class ErrorHandler {
|
|
|
37
63
|
const logFilePath = path.logsPath('stacks.log') ?? path.logsPath('errors.log')
|
|
38
64
|
|
|
39
65
|
try {
|
|
40
|
-
|
|
41
|
-
await
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} catch (error) {
|
|
66
|
+
await mkdir(path.dirname(logFilePath), { recursive: true })
|
|
67
|
+
await appendFile(logFilePath, formattedError)
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
45
70
|
console.error('Failed to write to error file:', error)
|
|
46
71
|
}
|
|
47
72
|
}
|
|
48
73
|
|
|
49
74
|
static writeErrorToConsole(err: string | Error | unknown): void {
|
|
50
|
-
if (
|
|
51
|
-
err === 'Failed to execute command: bunx biome check --fix' ||
|
|
52
|
-
err === 'Failed to execute command: bun --bun storage/framework/core/actions/src/lint/fix.ts'
|
|
53
|
-
)
|
|
54
|
-
// To trigger this, run `buddy release` with a lint error in your codebase
|
|
55
|
-
console.error(err)
|
|
56
|
-
process.exit(ExitCode.FatalError) // TODO: abstract this by differently catching the error somewhere
|
|
57
75
|
console.error(err)
|
|
76
|
+
|
|
77
|
+
const errorString = typeof err === 'string' ? err : err instanceof Error ? err.message : JSON.stringify(err)
|
|
78
|
+
|
|
79
|
+
if (
|
|
80
|
+
errorString.includes('bunx --bun cdk destroy')
|
|
81
|
+
|| errorString === `Failed to execute command: ${italic('bunx --bun eslint . --fix')}`
|
|
82
|
+
|| errorString === `Failed to execute command: ${italic('bun storage/framework/core/actions/src/lint/fix.ts')}`
|
|
83
|
+
) {
|
|
84
|
+
if (!this.isTestEnvironment) {
|
|
85
|
+
// eslint-disable-next-line no-console
|
|
86
|
+
console.log(
|
|
87
|
+
'No need to worry. The edge function is currently being destroyed. Please run `buddy undeploy` shortly again, and continue doing so until it succeeds running.',
|
|
88
|
+
)
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log('Hoping to see you back soon!')
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (this.shouldExitProcess) {
|
|
95
|
+
process.exit(ExitCode.FatalError)
|
|
96
|
+
}
|
|
58
97
|
}
|
|
59
98
|
}
|
|
60
99
|
|
|
61
|
-
|
|
100
|
+
export function handleError(err: string | Error | object | unknown, options?: ErrorOptions): Error {
|
|
101
|
+
let errorMessage: string
|
|
102
|
+
|
|
103
|
+
if (isString(err)) {
|
|
104
|
+
errorMessage = err
|
|
105
|
+
}
|
|
106
|
+
else if (err instanceof Error) {
|
|
107
|
+
errorMessage = err.message
|
|
108
|
+
}
|
|
109
|
+
else if (options instanceof Error) {
|
|
110
|
+
errorMessage = options.message
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
errorMessage = String(err)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
writeToLogFile(`ERROR: ${stripAnsi(errorMessage)}`)
|
|
62
117
|
|
|
63
|
-
export function handleError(err: ErrorDescription | Error | unknown, options?: ErrorOptions): Error {
|
|
64
118
|
return ErrorHandler.handle(err, options)
|
|
65
119
|
}
|
|
120
|
+
|
|
121
|
+
interface WriteOptions {
|
|
122
|
+
logFile?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export async function writeToLogFile(message: string, options?: WriteOptions): Promise<void> {
|
|
126
|
+
const formattedMessage = `[${new Date().toISOString()}] ${message}\n`
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const logFile = options?.logFile ?? config.logging.logsPath ?? 'storage/logs/stacks.log'
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
// Check if the file exists
|
|
133
|
+
await access(logFile)
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// File doesn't exist, create the directory
|
|
137
|
+
// eslint-disable-next-line no-console
|
|
138
|
+
console.log('Creating log file directory...', logFile)
|
|
139
|
+
await mkdir(dirname(logFile), { recursive: true })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Append the message to the log file
|
|
143
|
+
await appendFile(logFile, formattedMessage)
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error('Failed to write to log file:', error)
|
|
147
|
+
}
|
|
148
|
+
}
|
package/src/http.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
export * from './handler'
|
|
2
|
+
export * from './http'
|
|
3
|
+
export * from './utils'
|
|
2
4
|
export {
|
|
3
5
|
err,
|
|
6
|
+
Err,
|
|
4
7
|
errAsync,
|
|
5
8
|
fromPromise,
|
|
6
9
|
fromSafePromise,
|
|
7
10
|
fromThrowable,
|
|
8
11
|
ok,
|
|
9
|
-
okAsync,
|
|
10
|
-
Err,
|
|
11
12
|
Ok,
|
|
13
|
+
okAsync,
|
|
12
14
|
Result,
|
|
13
15
|
ResultAsync,
|
|
14
16
|
} from 'neverthrow'
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ErrorHandler } from './handler'
|
|
2
|
+
|
|
3
|
+
export function rescue<T, F>(
|
|
4
|
+
fn: () => T | Promise<T>,
|
|
5
|
+
fallback: F,
|
|
6
|
+
onError?: (error: Error) => void,
|
|
7
|
+
): T | F | Promise<T | F> {
|
|
8
|
+
try {
|
|
9
|
+
const result = fn()
|
|
10
|
+
if (result instanceof Promise) {
|
|
11
|
+
return result.catch((error) => {
|
|
12
|
+
if (onError) {
|
|
13
|
+
onError(ErrorHandler.handle(error))
|
|
14
|
+
}
|
|
15
|
+
return fallback
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
return result
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (onError) {
|
|
22
|
+
onError(ErrorHandler.handle(error))
|
|
23
|
+
}
|
|
24
|
+
return fallback
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Server Error</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
background-color: #f4f4f4;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 20px;
|
|
13
|
+
}
|
|
14
|
+
.error-container {
|
|
15
|
+
background-color: #ffefef;
|
|
16
|
+
border: 1px solid #ff4d4d;
|
|
17
|
+
color: #d8000c;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
border-radius: 5px;
|
|
20
|
+
}
|
|
21
|
+
.error-title {
|
|
22
|
+
font-size: 24px;
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
margin-bottom: 10px;
|
|
25
|
+
}
|
|
26
|
+
.error-message {
|
|
27
|
+
font-size: 18px;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<div class="error-container">
|
|
33
|
+
<div class="error-title">An Error Occurred</div>
|
|
34
|
+
<p class="error-message">{{ERROR_MESSAGE}}</p>
|
|
35
|
+
</div>
|
|
36
|
+
</body>
|
|
37
|
+
</html>
|