@deployanyway/error-translator 0.3.0 → 1.0.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/CHANGELOG.md +14 -0
- package/MIGRATION.md +12 -0
- package/README.md +97 -9
- package/dist/index.cjs +589 -0
- package/examples/basic.js +9 -0
- package/examples/explain-failure.mjs +11 -0
- package/index.d.cts +33 -2
- package/index.d.ts +33 -2
- package/package.json +7 -4
- package/src/cli.js +39 -1
- package/src/definitions.js +2 -0
- package/src/diagnostics.js +79 -0
- package/src/extra-definitions.js +494 -0
- package/src/index.js +55 -0
package/index.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
1
|
+
export interface ErrorRecord {
|
|
2
|
+
code?: string;
|
|
3
|
+
name?: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
cause?: unknown;
|
|
7
|
+
}
|
|
8
|
+
export type ErrorInput = string | Error | ErrorRecord;
|
|
3
9
|
export interface TranslationOptions {
|
|
4
10
|
mode?: "plain" | "rubber-duck";
|
|
5
11
|
}
|
|
@@ -21,3 +27,28 @@ export function translateErrors(
|
|
|
21
27
|
): Translation[];
|
|
22
28
|
export function renderTranslation(result: Translation): string;
|
|
23
29
|
export function listErrors(): string[];
|
|
30
|
+
|
|
31
|
+
export function errorCatalog(options?: TranslationOptions): Translation[];
|
|
32
|
+
export interface DiagnosisOptions extends TranslationOptions {
|
|
33
|
+
maxDepth?: number;
|
|
34
|
+
includeStack?: boolean;
|
|
35
|
+
}
|
|
36
|
+
export interface DiagnosticLayer {
|
|
37
|
+
depth: number;
|
|
38
|
+
name: string;
|
|
39
|
+
message: string;
|
|
40
|
+
code?: string;
|
|
41
|
+
translation: Translation;
|
|
42
|
+
stack?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface Diagnosis {
|
|
45
|
+
readonly original: ErrorInput;
|
|
46
|
+
chain: DiagnosticLayer[];
|
|
47
|
+
stopped: "complete" | "cycle" | "depth-limit";
|
|
48
|
+
truncated: boolean;
|
|
49
|
+
}
|
|
50
|
+
export function diagnoseError(
|
|
51
|
+
error: ErrorInput,
|
|
52
|
+
options?: DiagnosisOptions,
|
|
53
|
+
): Diagnosis;
|
|
54
|
+
export function renderDiagnosis(result: Diagnosis): string;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deployanyway/error-translator",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Explain real Node.js errors and cause chains with practical debugging checks. The stack trace has chosen violence; the duck brought context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -27,7 +27,8 @@
|
|
|
27
27
|
"dist",
|
|
28
28
|
"index.d.ts",
|
|
29
29
|
"index.d.cts",
|
|
30
|
-
"MIGRATION.md"
|
|
30
|
+
"MIGRATION.md",
|
|
31
|
+
"examples"
|
|
31
32
|
],
|
|
32
33
|
"engines": {
|
|
33
34
|
"node": ">=22.13"
|
|
@@ -74,7 +75,9 @@
|
|
|
74
75
|
"plain-english",
|
|
75
76
|
"typescript",
|
|
76
77
|
"humor",
|
|
77
|
-
"deployanyway"
|
|
78
|
+
"deployanyway",
|
|
79
|
+
"error-cause",
|
|
80
|
+
"error-handling"
|
|
78
81
|
],
|
|
79
82
|
"main": "./dist/index.cjs",
|
|
80
83
|
"types": "./index.d.ts",
|
package/src/cli.js
CHANGED
|
@@ -6,6 +6,9 @@ import {
|
|
|
6
6
|
renderTranslation,
|
|
7
7
|
translateErrors,
|
|
8
8
|
listErrors,
|
|
9
|
+
errorCatalog,
|
|
10
|
+
diagnoseError,
|
|
11
|
+
renderDiagnosis,
|
|
9
12
|
} from "./index.js";
|
|
10
13
|
|
|
11
14
|
import { readStdin } from "./input.js";
|
|
@@ -19,12 +22,16 @@ try {
|
|
|
19
22
|
json: { type: "boolean" },
|
|
20
23
|
batch: { type: "boolean" },
|
|
21
24
|
list: { type: "boolean" },
|
|
25
|
+
catalog: { type: "boolean" },
|
|
26
|
+
diagnose: { type: "boolean" },
|
|
27
|
+
"include-stack": { type: "boolean" },
|
|
28
|
+
"max-depth": { type: "string" },
|
|
22
29
|
mode: { type: "string", default: "plain" },
|
|
23
30
|
},
|
|
24
31
|
});
|
|
25
32
|
if (values.help) {
|
|
26
33
|
console.log(
|
|
27
|
-
'Usage: error-translator <error code or quoted message> [--json] [--mode plain|rubber-duck]\n\nTranslate errors into human-readable guidance. Without arguments, read stdin (256 KiB). --batch reads a JSON array of 1–100 errors. --list lists supported codes.\n\nOptions:\n -h, --help Show help\n -v, --version Show version\n --json Print a structured JSON result\n --mode plain|rubber-duck Keep useful guidance; add duck commentary\n\nExamples:\n error-translator ECONNREFUSED\n error-translator "TypeError: value is not a function" --json\n\nExit codes: 0 translation/help/version; 2 invalid arguments.',
|
|
34
|
+
'Usage: error-translator <error code or quoted message> [--json] [--mode plain|rubber-duck]\n\nTranslate errors into human-readable guidance. --diagnose reads a JSON error/cause record; --max-depth 1..32 bounds it; --include-stack opts into original stacks. Without arguments, read stdin (256 KiB). --batch reads a JSON array of 1–100 errors. --list lists supported codes. --catalog prints all explanations and checks.\n\nOptions:\n -h, --help Show help\n -v, --version Show version\n --json Print a structured JSON result\n --mode plain|rubber-duck Keep useful guidance; add duck commentary\n\nExamples:\n error-translator ECONNREFUSED\n error-translator "TypeError: value is not a function" --json\n\nExit codes: 0 translation/help/version; 2 invalid arguments.',
|
|
28
35
|
);
|
|
29
36
|
} else if (values.version) {
|
|
30
37
|
console.log(
|
|
@@ -32,6 +39,37 @@ try {
|
|
|
32
39
|
readFileSync(new URL("../package.json", import.meta.url), "utf8"),
|
|
33
40
|
).version,
|
|
34
41
|
);
|
|
42
|
+
} else if (values.diagnose) {
|
|
43
|
+
if (values.batch || values.catalog || values.list)
|
|
44
|
+
throw new TypeError(
|
|
45
|
+
"--diagnose cannot combine with --batch, --catalog or --list.",
|
|
46
|
+
);
|
|
47
|
+
const text = positionals.length ? positionals.join(" ") : await readStdin();
|
|
48
|
+
const result = diagnoseError(JSON.parse(text), {
|
|
49
|
+
mode: values.mode,
|
|
50
|
+
includeStack: values["include-stack"] ?? false,
|
|
51
|
+
...(values["max-depth"] !== undefined
|
|
52
|
+
? { maxDepth: Number(values["max-depth"]) }
|
|
53
|
+
: {}),
|
|
54
|
+
});
|
|
55
|
+
console.log(
|
|
56
|
+
values.json ? JSON.stringify(result, null, 2) : renderDiagnosis(result),
|
|
57
|
+
);
|
|
58
|
+
} else if (values["include-stack"] || values["max-depth"] !== undefined) {
|
|
59
|
+
throw new TypeError("--include-stack and --max-depth require --diagnose.");
|
|
60
|
+
} else if (values.catalog) {
|
|
61
|
+
if (positionals.length || values.batch || values.list)
|
|
62
|
+
throw new TypeError(
|
|
63
|
+
"--catalog does not accept input, --batch or --list.",
|
|
64
|
+
);
|
|
65
|
+
const catalog = errorCatalog({ mode: values.mode });
|
|
66
|
+
console.log(
|
|
67
|
+
values.json
|
|
68
|
+
? JSON.stringify(catalog, null, 2)
|
|
69
|
+
: catalog
|
|
70
|
+
.map((item) => item.code + " — " + renderTranslation(item))
|
|
71
|
+
.join("\n\n---\n\n"),
|
|
72
|
+
);
|
|
35
73
|
} else if (values.list) {
|
|
36
74
|
if (positionals.length || values.batch)
|
|
37
75
|
throw new TypeError("--list does not accept input or --batch.");
|
package/src/definitions.js
CHANGED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { translateError, renderTranslation } from "./index.js";
|
|
2
|
+
|
|
3
|
+
/** Snapshot a bounded cause chain; preserve the original without serializing it. */
|
|
4
|
+
export function diagnoseError(error, options = {}) {
|
|
5
|
+
if (!options || typeof options !== "object" || Array.isArray(options))
|
|
6
|
+
throw new TypeError("Options must be an object.");
|
|
7
|
+
const maxDepth = options.maxDepth ?? 8,
|
|
8
|
+
includeStack = options.includeStack ?? false;
|
|
9
|
+
if (!Number.isInteger(maxDepth) || maxDepth < 1 || maxDepth > 32)
|
|
10
|
+
throw new RangeError("maxDepth must be an integer from 1 to 32.");
|
|
11
|
+
if (typeof includeStack !== "boolean")
|
|
12
|
+
throw new TypeError("includeStack must be a boolean.");
|
|
13
|
+
// Validate the root using the established translation contract.
|
|
14
|
+
translateError(error, options);
|
|
15
|
+
const seen = new Set(),
|
|
16
|
+
chain = [];
|
|
17
|
+
let current = error,
|
|
18
|
+
stopped = "complete";
|
|
19
|
+
while (current !== undefined) {
|
|
20
|
+
if (chain.length === maxDepth) {
|
|
21
|
+
stopped = "depth-limit";
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
if (current !== null && typeof current === "object") {
|
|
25
|
+
if (seen.has(current)) {
|
|
26
|
+
stopped = "cycle";
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
seen.add(current);
|
|
30
|
+
}
|
|
31
|
+
const object =
|
|
32
|
+
current !== null && typeof current === "object" && !Array.isArray(current)
|
|
33
|
+
? current
|
|
34
|
+
: undefined;
|
|
35
|
+
const raw = typeof current === "string" ? current : object?.message;
|
|
36
|
+
const message = typeof raw === "string" ? raw : String(current);
|
|
37
|
+
const name = typeof object?.name === "string" ? object.name : "Error";
|
|
38
|
+
const code = typeof object?.code === "string" ? object.code : undefined;
|
|
39
|
+
const translation = translateError(
|
|
40
|
+
{ name, message, ...(code ? { code } : {}) },
|
|
41
|
+
options,
|
|
42
|
+
);
|
|
43
|
+
chain.push({
|
|
44
|
+
depth: chain.length,
|
|
45
|
+
name,
|
|
46
|
+
message,
|
|
47
|
+
...(code ? { code } : {}),
|
|
48
|
+
translation,
|
|
49
|
+
...(includeStack && typeof object?.stack === "string"
|
|
50
|
+
? { stack: object.stack }
|
|
51
|
+
: {}),
|
|
52
|
+
});
|
|
53
|
+
current = object?.cause;
|
|
54
|
+
}
|
|
55
|
+
const result = { chain, stopped, truncated: stopped !== "complete" };
|
|
56
|
+
Object.defineProperty(result, "original", {
|
|
57
|
+
value: error,
|
|
58
|
+
enumerable: false,
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
export function renderDiagnosis(result) {
|
|
63
|
+
if (
|
|
64
|
+
!result ||
|
|
65
|
+
!Array.isArray(result.chain) ||
|
|
66
|
+
!result.chain.length ||
|
|
67
|
+
!["complete", "cycle", "depth-limit"].includes(result.stopped)
|
|
68
|
+
)
|
|
69
|
+
throw new TypeError("Provide a diagnosis result.");
|
|
70
|
+
return (
|
|
71
|
+
result.chain
|
|
72
|
+
.map(
|
|
73
|
+
(item, index) =>
|
|
74
|
+
`${index === 0 ? "Failure" : "Caused by"}: ${item.name}${item.code ? " [" + item.code + "]" : ""}: ${item.message}\n${renderTranslation(item.translation)}${item.stack ? "\n\nOriginal stack:\n" + item.stack : ""}`,
|
|
75
|
+
)
|
|
76
|
+
.join("\n\n---\n\n") +
|
|
77
|
+
(result.truncated ? `\n\nCause traversal stopped: ${result.stopped}.` : "")
|
|
78
|
+
);
|
|
79
|
+
}
|