@tryghost/errors 1.2.27 → 1.3.1

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,64 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => {
4
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
5
+ return value;
6
+ };
7
+ import { v1 as uuidv1 } from "uuid";
8
+ import { wrapStack } from "./wrap-stack";
9
+ class GhostError extends Error {
10
+ constructor(options = {}) {
11
+ super();
12
+ __publicField(this, "statusCode");
13
+ __publicField(this, "errorType");
14
+ __publicField(this, "level");
15
+ __publicField(this, "id");
16
+ __publicField(this, "context");
17
+ __publicField(this, "help");
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ __publicField(this, "errorDetails");
20
+ __publicField(this, "code");
21
+ __publicField(this, "property");
22
+ __publicField(this, "redirect");
23
+ __publicField(this, "hideStack");
24
+ this.statusCode = 500;
25
+ this.errorType = "InternalServerError";
26
+ this.level = "normal";
27
+ this.message = "The server has encountered an error.";
28
+ this.id = uuidv1();
29
+ this.id = options.id || this.id;
30
+ this.statusCode = options.statusCode || this.statusCode;
31
+ this.level = options.level || this.level;
32
+ this.context = options.context;
33
+ this.help = options.help;
34
+ this.errorType = this.name = options.errorType || this.errorType;
35
+ this.errorDetails = options.errorDetails;
36
+ this.code = options.code || null;
37
+ this.property = options.property || null;
38
+ this.redirect = options.redirect || null;
39
+ this.message = options.message || this.message;
40
+ this.hideStack = options.hideStack || false;
41
+ if (options.err) {
42
+ if (typeof options.err === "string") {
43
+ options.err = new Error(options.err);
44
+ }
45
+ Object.getOwnPropertyNames(options.err).forEach((property) => {
46
+ if (["errorType", "name", "statusCode", "message", "level"].indexOf(property) !== -1) {
47
+ return;
48
+ }
49
+ if (property === "code") {
50
+ this[property] = this[property] || options.err[property];
51
+ return;
52
+ }
53
+ if (property === "stack" && !this.hideStack) {
54
+ this[property] = wrapStack(this, options.err);
55
+ return;
56
+ }
57
+ this[property] = options.err[property] || this[property];
58
+ });
59
+ }
60
+ }
61
+ }
62
+ export {
63
+ GhostError
64
+ };
package/es/errors.js ADDED
@@ -0,0 +1,310 @@
1
+ import { GhostError } from "./GhostError";
2
+ const mergeOptions = (options, defaults) => {
3
+ const result = { ...defaults };
4
+ Object.keys(options).forEach((key) => {
5
+ if (options[key] !== void 0) {
6
+ result[key] = options[key];
7
+ }
8
+ });
9
+ return result;
10
+ };
11
+ class InternalServerError extends GhostError {
12
+ constructor(options = {}) {
13
+ super(mergeOptions(options, {
14
+ statusCode: 500,
15
+ level: "critical",
16
+ errorType: "InternalServerError",
17
+ message: "The server has encountered an error."
18
+ }));
19
+ }
20
+ }
21
+ class IncorrectUsageError extends GhostError {
22
+ constructor(options = {}) {
23
+ super(mergeOptions(options, {
24
+ statusCode: 400,
25
+ level: "critical",
26
+ errorType: "IncorrectUsageError",
27
+ message: "We detected a misuse. Please read the stack trace."
28
+ }));
29
+ }
30
+ }
31
+ class NotFoundError extends GhostError {
32
+ constructor(options = {}) {
33
+ super(mergeOptions(options, {
34
+ statusCode: 404,
35
+ errorType: "NotFoundError",
36
+ message: "Resource could not be found.",
37
+ hideStack: true
38
+ }));
39
+ }
40
+ }
41
+ class BadRequestError extends GhostError {
42
+ constructor(options = {}) {
43
+ super(mergeOptions(options, {
44
+ statusCode: 400,
45
+ errorType: "BadRequestError",
46
+ message: "The request could not be understood."
47
+ }));
48
+ }
49
+ }
50
+ class UnauthorizedError extends GhostError {
51
+ constructor(options = {}) {
52
+ super(mergeOptions(options, {
53
+ statusCode: 401,
54
+ errorType: "UnauthorizedError",
55
+ message: "You are not authorised to make this request."
56
+ }));
57
+ }
58
+ }
59
+ class NoPermissionError extends GhostError {
60
+ constructor(options = {}) {
61
+ super(mergeOptions(options, {
62
+ statusCode: 403,
63
+ errorType: "NoPermissionError",
64
+ message: "You do not have permission to perform this request."
65
+ }));
66
+ }
67
+ }
68
+ class ValidationError extends GhostError {
69
+ constructor(options = {}) {
70
+ super(mergeOptions(options, {
71
+ statusCode: 422,
72
+ errorType: "ValidationError",
73
+ message: "The request failed validation."
74
+ }));
75
+ }
76
+ }
77
+ class UnsupportedMediaTypeError extends GhostError {
78
+ constructor(options = {}) {
79
+ super(mergeOptions(options, {
80
+ statusCode: 415,
81
+ errorType: "UnsupportedMediaTypeError",
82
+ message: "The media in the request is not supported by the server."
83
+ }));
84
+ }
85
+ }
86
+ class TooManyRequestsError extends GhostError {
87
+ constructor(options = {}) {
88
+ super(mergeOptions(options, {
89
+ statusCode: 429,
90
+ errorType: "TooManyRequestsError",
91
+ message: "Server has received too many similar requests in a short space of time."
92
+ }));
93
+ }
94
+ }
95
+ class MaintenanceError extends GhostError {
96
+ constructor(options = {}) {
97
+ super(mergeOptions(options, {
98
+ statusCode: 503,
99
+ errorType: "MaintenanceError",
100
+ message: "The server is temporarily down for maintenance."
101
+ }));
102
+ }
103
+ }
104
+ class MethodNotAllowedError extends GhostError {
105
+ constructor(options = {}) {
106
+ super(mergeOptions(options, {
107
+ statusCode: 405,
108
+ errorType: "MethodNotAllowedError",
109
+ message: "Method not allowed for resource."
110
+ }));
111
+ }
112
+ }
113
+ class RequestNotAcceptableError extends GhostError {
114
+ constructor(options = {}) {
115
+ super(mergeOptions(options, {
116
+ statusCode: 406,
117
+ errorType: "RequestNotAcceptableError",
118
+ message: "Request not acceptable for provided Accept-Version header.",
119
+ hideStack: true
120
+ }));
121
+ }
122
+ }
123
+ class RangeNotSatisfiableError extends GhostError {
124
+ constructor(options = {}) {
125
+ super(mergeOptions(options, {
126
+ statusCode: 416,
127
+ errorType: "RangeNotSatisfiableError",
128
+ message: "Range not satisfiable for provided Range header.",
129
+ hideStack: true
130
+ }));
131
+ }
132
+ }
133
+ class RequestEntityTooLargeError extends GhostError {
134
+ constructor(options = {}) {
135
+ super(mergeOptions(options, {
136
+ statusCode: 413,
137
+ errorType: "RequestEntityTooLargeError",
138
+ message: "Request was too big for the server to handle."
139
+ }));
140
+ }
141
+ }
142
+ class TokenRevocationError extends GhostError {
143
+ constructor(options = {}) {
144
+ super(mergeOptions(options, {
145
+ statusCode: 503,
146
+ errorType: "TokenRevocationError",
147
+ message: "Token is no longer available."
148
+ }));
149
+ }
150
+ }
151
+ class VersionMismatchError extends GhostError {
152
+ constructor(options = {}) {
153
+ super(mergeOptions(options, {
154
+ statusCode: 400,
155
+ errorType: "VersionMismatchError",
156
+ message: "Requested version does not match server version."
157
+ }));
158
+ }
159
+ }
160
+ class DataExportError extends GhostError {
161
+ constructor(options = {}) {
162
+ super(mergeOptions(options, {
163
+ statusCode: 500,
164
+ errorType: "DataExportError",
165
+ message: "The server encountered an error whilst exporting data."
166
+ }));
167
+ }
168
+ }
169
+ class DataImportError extends GhostError {
170
+ constructor(options = {}) {
171
+ super(mergeOptions(options, {
172
+ statusCode: 500,
173
+ errorType: "DataImportError",
174
+ message: "The server encountered an error whilst importing data."
175
+ }));
176
+ }
177
+ }
178
+ class EmailError extends GhostError {
179
+ constructor(options = {}) {
180
+ super(mergeOptions(options, {
181
+ statusCode: 500,
182
+ errorType: "EmailError",
183
+ message: "The server encountered an error whilst sending email."
184
+ }));
185
+ }
186
+ }
187
+ class ThemeValidationError extends GhostError {
188
+ constructor(options = {}) {
189
+ super(mergeOptions(options, {
190
+ statusCode: 422,
191
+ errorType: "ThemeValidationError",
192
+ message: "The theme has a validation error.",
193
+ errorDetails: {}
194
+ }));
195
+ }
196
+ }
197
+ class DisabledFeatureError extends GhostError {
198
+ constructor(options = {}) {
199
+ super(mergeOptions(options, {
200
+ statusCode: 409,
201
+ errorType: "DisabledFeatureError",
202
+ message: "Unable to complete the request, this feature is disabled."
203
+ }));
204
+ }
205
+ }
206
+ class UpdateCollisionError extends GhostError {
207
+ constructor(options = {}) {
208
+ super(mergeOptions(options, {
209
+ statusCode: 409,
210
+ errorType: "UpdateCollisionError",
211
+ message: "Unable to complete the request, there was a conflict."
212
+ }));
213
+ }
214
+ }
215
+ class HostLimitError extends GhostError {
216
+ constructor(options = {}) {
217
+ super(mergeOptions(options, {
218
+ errorType: "HostLimitError",
219
+ hideStack: true,
220
+ statusCode: 403,
221
+ message: "Unable to complete the request, this resource is limited."
222
+ }));
223
+ }
224
+ }
225
+ class HelperWarning extends GhostError {
226
+ constructor(options = {}) {
227
+ super(mergeOptions(options, {
228
+ errorType: "HelperWarning",
229
+ hideStack: true,
230
+ statusCode: 400,
231
+ message: "A theme helper has done something unexpected."
232
+ }));
233
+ }
234
+ }
235
+ class PasswordResetRequiredError extends GhostError {
236
+ constructor(options = {}) {
237
+ super(mergeOptions(options, {
238
+ errorType: "PasswordResetRequiredError",
239
+ statusCode: 401,
240
+ message: "For security, you need to create a new password. An email has been sent to you with instructions!"
241
+ }));
242
+ }
243
+ }
244
+ class UnhandledJobError extends GhostError {
245
+ constructor(options = {}) {
246
+ super(mergeOptions(options, {
247
+ errorType: "UnhandledJobError",
248
+ message: "Processed job threw an unhandled error",
249
+ level: "critical"
250
+ }));
251
+ }
252
+ }
253
+ class NoContentError extends GhostError {
254
+ constructor(options = {}) {
255
+ super(mergeOptions(options, {
256
+ errorType: "NoContentError",
257
+ statusCode: 204,
258
+ hideStack: true
259
+ }));
260
+ }
261
+ }
262
+ class ConflictError extends GhostError {
263
+ constructor(options = {}) {
264
+ super(mergeOptions(options, {
265
+ errorType: "ConflictError",
266
+ statusCode: 409,
267
+ message: "The server has encountered an conflict."
268
+ }));
269
+ }
270
+ }
271
+ class MigrationError extends GhostError {
272
+ constructor(options = {}) {
273
+ super(mergeOptions(options, {
274
+ errorType: "MigrationError",
275
+ message: "An error has occurred applying a database migration.",
276
+ level: "critical"
277
+ }));
278
+ }
279
+ }
280
+ export {
281
+ BadRequestError,
282
+ ConflictError,
283
+ DataExportError,
284
+ DataImportError,
285
+ DisabledFeatureError,
286
+ EmailError,
287
+ HelperWarning,
288
+ HostLimitError,
289
+ IncorrectUsageError,
290
+ InternalServerError,
291
+ MaintenanceError,
292
+ MethodNotAllowedError,
293
+ MigrationError,
294
+ NoContentError,
295
+ NoPermissionError,
296
+ NotFoundError,
297
+ PasswordResetRequiredError,
298
+ RangeNotSatisfiableError,
299
+ RequestEntityTooLargeError,
300
+ RequestNotAcceptableError,
301
+ ThemeValidationError,
302
+ TokenRevocationError,
303
+ TooManyRequestsError,
304
+ UnauthorizedError,
305
+ UnhandledJobError,
306
+ UnsupportedMediaTypeError,
307
+ UpdateCollisionError,
308
+ ValidationError,
309
+ VersionMismatchError
310
+ };
package/es/index.js ADDED
@@ -0,0 +1,14 @@
1
+ import * as ghostErrors from "./errors";
2
+ import { deserialize, isGhostError, prepareStackForUser, serialize } from "./utils";
3
+ export * from "./errors";
4
+ var src_default = ghostErrors;
5
+ const utils = {
6
+ serialize,
7
+ deserialize,
8
+ isGhostError,
9
+ prepareStackForUser
10
+ };
11
+ export {
12
+ src_default as default,
13
+ utils
14
+ };
package/es/utils.js ADDED
@@ -0,0 +1,174 @@
1
+ import deepCopy from "@stdlib/utils-copy";
2
+ import { GhostError } from "./GhostError";
3
+ import * as errors from "./errors";
4
+ const errorsWithBase = { ...errors, GhostError };
5
+ const _private = {
6
+ serialize(err) {
7
+ try {
8
+ return {
9
+ id: err.id,
10
+ status: err.statusCode,
11
+ code: err.code || err.errorType,
12
+ title: err.name,
13
+ detail: err.message,
14
+ meta: {
15
+ context: err.context,
16
+ help: err.help,
17
+ errorDetails: err.errorDetails,
18
+ level: err.level,
19
+ errorType: err.errorType
20
+ }
21
+ };
22
+ } catch (error) {
23
+ return {
24
+ detail: "Something went wrong."
25
+ };
26
+ }
27
+ },
28
+ deserialize(obj) {
29
+ return {
30
+ id: obj.id,
31
+ message: obj.detail || obj.error_description || obj.message,
32
+ statusCode: obj.status,
33
+ code: obj.code || obj.error,
34
+ level: obj.meta && obj.meta.level,
35
+ help: obj.meta && obj.meta.help,
36
+ context: obj.meta && obj.meta.context
37
+ };
38
+ },
39
+ /**
40
+ * @description Serialize error instance into oauth format.
41
+ *
42
+ * @see https://tools.ietf.org/html/rfc6749#page-45
43
+ *
44
+ * To not loose any error data when sending errors between internal services, we use the suggested OAuth properties and add ours as well.
45
+ */
46
+ OAuthSerialize(err) {
47
+ const matchTable = {
48
+ [errors.NoPermissionError.name]: "access_denied",
49
+ [errors.MaintenanceError.name]: "temporarily_unavailable",
50
+ [errors.BadRequestError.name]: "invalid_request",
51
+ [errors.ValidationError.name]: "invalid_request",
52
+ default: "server_error"
53
+ };
54
+ const { detail, code, ...properties } = _private.serialize(err);
55
+ return {
56
+ error: err.code || matchTable[err.name] || "server_error",
57
+ error_description: err.message,
58
+ ...properties
59
+ };
60
+ },
61
+ /**
62
+ * @description Deserialize oauth error format into GhostError instance.
63
+ * @constructor
64
+ */
65
+ OAuthDeserialize(errorFormat) {
66
+ try {
67
+ return new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
68
+ } catch (err) {
69
+ return new errors.InternalServerError({
70
+ errorType: errorFormat.title || errorFormat.name,
71
+ ..._private.deserialize(errorFormat)
72
+ });
73
+ }
74
+ },
75
+ /**
76
+ * @description Serialize GhostError instance into jsonapi.org format.
77
+ * @param err
78
+ * @return {Object}
79
+ */
80
+ JSONAPISerialize(err) {
81
+ const errorFormat = {
82
+ errors: [_private.serialize(err)]
83
+ };
84
+ errorFormat.errors[0].source = {};
85
+ if (err.property) {
86
+ errorFormat.errors[0].source.pointer = "/data/attributes/" + err.property;
87
+ }
88
+ return errorFormat;
89
+ },
90
+ /**
91
+ * @description Deserialize JSON api format into GhostError instance.
92
+ */
93
+ JSONAPIDeserialize(errorFormat) {
94
+ errorFormat = errorFormat.errors && errorFormat.errors[0] || {};
95
+ let internalError;
96
+ try {
97
+ internalError = new errorsWithBase[errorFormat.title || errorFormat.name || errors.InternalServerError.name](_private.deserialize(errorFormat));
98
+ } catch (err) {
99
+ internalError = new errors.InternalServerError({
100
+ errorType: errorFormat.title || errorFormat.name,
101
+ ..._private.deserialize(errorFormat)
102
+ });
103
+ }
104
+ if (errorFormat.source && errorFormat.source.pointer) {
105
+ internalError.property = errorFormat.source.pointer.split("/")[3];
106
+ }
107
+ return internalError;
108
+ }
109
+ };
110
+ function serialize(err, options) {
111
+ options = options || { format: "jsonapi" };
112
+ let errorFormat = {};
113
+ try {
114
+ if (options.format === "jsonapi") {
115
+ errorFormat = _private.JSONAPISerialize(err);
116
+ } else {
117
+ errorFormat = _private.OAuthSerialize(err);
118
+ }
119
+ } catch (error) {
120
+ errorFormat.message = "Something went wrong.";
121
+ }
122
+ return errorFormat;
123
+ }
124
+ ;
125
+ function deserialize(errorFormat) {
126
+ let internalError = {};
127
+ if (errorFormat.errors) {
128
+ internalError = _private.JSONAPIDeserialize(errorFormat);
129
+ } else {
130
+ internalError = _private.OAuthDeserialize(errorFormat);
131
+ }
132
+ return internalError;
133
+ }
134
+ ;
135
+ function prepareStackForUser(error) {
136
+ const stackbits = error.stack?.split(/\n/) || [];
137
+ const hideStack = "hideStack" in error && error.hideStack;
138
+ if (hideStack) {
139
+ stackbits.splice(1, stackbits.length - 1);
140
+ } else {
141
+ stackbits.splice(1, 0, `Stack Trace:`);
142
+ }
143
+ if ("help" in error && error.help) {
144
+ stackbits.splice(1, 0, `${error.help}`);
145
+ }
146
+ if ("context" in error && error.context) {
147
+ stackbits.splice(1, 0, `${error.context}`);
148
+ }
149
+ const errorClone = deepCopy(error);
150
+ errorClone.stack = stackbits.join("\n");
151
+ return errorClone;
152
+ }
153
+ ;
154
+ function isGhostError(err) {
155
+ const errorName = GhostError.name;
156
+ const legacyErrorName = "IgnitionError";
157
+ const recursiveIsGhostError = function recursiveIsGhostError2(obj) {
158
+ if (!obj || !obj.name) {
159
+ return false;
160
+ }
161
+ if (obj.name === errorName || obj.name === legacyErrorName) {
162
+ return true;
163
+ }
164
+ return recursiveIsGhostError2(Object.getPrototypeOf(obj));
165
+ };
166
+ return recursiveIsGhostError(err.constructor);
167
+ }
168
+ ;
169
+ export {
170
+ deserialize,
171
+ isGhostError,
172
+ prepareStackForUser,
173
+ serialize
174
+ };
@@ -0,0 +1,9 @@
1
+ function wrapStack(err, internalErr) {
2
+ const extraLine = (err.stack?.split(/\n/g) || [])[1];
3
+ const [firstLine, ...rest] = internalErr.stack?.split(/\n/g) || [];
4
+ return [firstLine, extraLine, ...rest].join("\n");
5
+ }
6
+ ;
7
+ export {
8
+ wrapStack
9
+ };
package/package.json CHANGED
@@ -1,33 +1,50 @@
1
1
  {
2
2
  "name": "@tryghost/errors",
3
- "version": "1.2.27",
3
+ "version": "1.3.1",
4
4
  "repository": "https://github.com/TryGhost/framework/tree/main/packages/errors",
5
5
  "author": "Ghost Foundation",
6
6
  "license": "MIT",
7
- "main": "index.js",
7
+ "main": "cjs/index.js",
8
+ "module": "es/index.js",
9
+ "types": "types/index.d.ts",
10
+ "source": "src/index.ts",
11
+ "sideEffects": false,
8
12
  "scripts": {
9
13
  "dev": "echo \"Implement me!\"",
10
- "test": "NODE_ENV=testing c8 --check-coverage --all --reporter text --reporter cobertura mocha './test/**/*.test.js'",
14
+ "prepare": "NODE_ENV=production yarn build",
15
+ "pretest": "NODE_ENV=production yarn build",
16
+ "build": "yarn build:cjs && yarn build:es && yarn build:types",
17
+ "build:cjs": "esbuild src/*.ts --target=es2020 --outdir=cjs --format=cjs",
18
+ "build:es": "esbuild src/*.ts --target=es2020 --outdir=es --format=esm",
19
+ "build:types": "tsc --emitDeclarationOnly --declaration --declarationMap --outDir types",
20
+ "test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' NODE_ENV=testing c8 --check-coverage --all -n src --reporter text --reporter cobertura mocha -r ts-node/register './test/**/*.test.ts'",
11
21
  "lint": "eslint . --ext .js --cache",
12
22
  "posttest": "yarn lint"
13
23
  },
14
24
  "files": [
15
- "index.js",
16
- "lib"
25
+ "cjs",
26
+ "es",
27
+ "types",
28
+ "src"
17
29
  ],
18
30
  "publishConfig": {
19
31
  "access": "public"
20
32
  },
21
33
  "devDependencies": {
22
- "c8": "8.0.1",
23
- "mocha": "10.2.0",
34
+ "@types/lodash": "^4.14.200",
35
+ "@types/mocha": "^10.0.3",
36
+ "@types/uuid": "^9.0.6",
37
+ "c8": "9.1.0",
38
+ "esbuild": "^0.19.5",
39
+ "lodash": "^4.17.21",
40
+ "mocha": "10.3.0",
24
41
  "should": "13.2.3",
25
- "sinon": "17.0.1"
42
+ "ts-node": "^10.9.1",
43
+ "typescript": "5.2.2"
26
44
  },
27
45
  "dependencies": {
28
46
  "@stdlib/utils-copy": "^0.0.7",
29
- "lodash": "^4.17.21",
30
47
  "uuid": "^9.0.0"
31
48
  },
32
- "gitHead": "a655bcfba6531944b8fd997bf53ccefb9dcf1685"
49
+ "gitHead": "46cfa9c0bc277226048af7c1b98c797bccc05db5"
33
50
  }