@stacksjs/error-handling 0.64.5 → 0.65.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.
@@ -0,0 +1,4 @@
1
+ export declare function rescue<
2
+ T,
3
+ F
4
+ >(fn: () => T | Promise<T>, fallback: F, onError?: (error: Error) => void): T | F | Promise<T | F>;
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@stacksjs/error-handling",
3
3
  "type": "module",
4
- "version": "0.64.5",
4
+ "version": "0.65.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
- "contributors": [
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 --bun build.ts",
46
- "typecheck": "bun --bun tsc --noEmit",
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/development": "latest"
38
+ "@stacksjs/cli": "0.64.6",
39
+ "@stacksjs/config": "0.64.6",
40
+ "@stacksjs/development": "0.64.6",
41
+ "@stacksjs/path": "0.64.6",
42
+ "@stacksjs/types": "0.64.6",
43
+ "@stacksjs/validation": "0.64.6",
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 fs from 'fs-extra'
9
+ import { isString } from '@stacksjs/validation'
4
10
 
5
- interface ErrorOptions {
6
- silent?: boolean
7
- }
8
-
9
- export const StacksError = Error
11
+ type ErrorMessage = string
10
12
 
11
13
  export class ErrorHandler {
12
- // static logFile = path.logsPath('errors.log')
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
- static handle(err: ErrorDescription | Error | unknown, options?: ErrorOptions): Error {
15
- // let's only write to the console if we are not in silent mode
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
- if (typeof err === 'string') err = new StacksError(err)
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(err).catch((e) => console.error(e))
46
+ this.writeErrorToFile(error).catch(e => console.error(e))
21
47
 
22
- return err as Error // TODO: improve this type
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
- // Ensure the directory exists
41
- await fs.mkdir(path.dirname(logFilePath), { recursive: true })
42
- // Append the message to the log file
43
- await fs.appendFile(logFilePath, formattedError)
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
- type ErrorDescription = string
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/index.ts CHANGED
@@ -1,14 +1,15 @@
1
1
  export * from './handler'
2
+ export * from './utils'
2
3
  export {
3
4
  err,
5
+ Err,
4
6
  errAsync,
5
7
  fromPromise,
6
8
  fromSafePromise,
7
9
  fromThrowable,
8
10
  ok,
9
- okAsync,
10
- Err,
11
11
  Ok,
12
+ okAsync,
12
13
  Result,
13
14
  ResultAsync,
14
15
  } 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
+ }