@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/index.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- export type ErrorInput =
2
- string | Error | { code?: string; name?: string; message?: string };
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.3.0",
4
- "description": "Plain-English Node.js error explanations and debugging tips, because the stack trace has chosen violence.",
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.");
@@ -1,5 +1,7 @@
1
+ import { extraDefinitions } from "./extra-definitions.js";
1
2
  // Keep technical guidance here so contributors can add errors without changing logic.
2
3
  export const definitions = {
4
+ ...extraDefinitions,
3
5
  ECONNREFUSED: {
4
6
  title: "Connection refused",
5
7
  explanation:
@@ -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
+ }