@deployanyway/error-translator 0.1.1 → 0.3.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 — 2026-10-08
4
+
5
+ - Useful structured API/CLI additions described in README.
6
+ - TypeScript declarations, CommonJS entry, coverage gates and installed archive checks.
7
+ - Linux Node 22/24 plus Windows/macOS Node 24 CI.
8
+
9
+ ## 0.2.0 — 2026-10-08
10
+
11
+ - Rubber-duck translations: Keep the debugging guidance, add workplace-safe rubber-duck commentary. Plain output remains the default.
12
+ - Add npm and CI badges to the published README.
13
+
3
14
  ## 0.1.1 — 2026-10-08
4
15
 
5
16
  - Correct npm installation and npx documentation after the initial publication.
package/MIGRATION.md ADDED
@@ -0,0 +1,5 @@
1
+ # 0.3.0 migration
2
+
3
+ Node 22.13+ is required. Existing core APIs remain available; TypeScript declarations and CommonJS exports are new. The CLI now lives in src/cli.js behind the same executable path. Input/stdout behavior for new options is documented in README.
4
+
5
+ Install 0.3.0 with npm. Seeds and exact humorous wording are version-specific. Do not treat jokes or heuristic scores as production evidence.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # error-translator
2
2
 
3
+ > **Version 0.3.0:** install from npm with Node 22.13+ or Node 24. See MIGRATION.md for changes from 0.2.0.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/%40deployanyway%2Ferror-translator)](https://www.npmjs.com/package/@deployanyway/error-translator)
6
+ [![CI](https://github.com/DeployAnyway/error-translator/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DeployAnyway/error-translator/actions/workflows/ci.yml)
7
+
3
8
  Plain-English Node.js error explanations and debugging tips, because the stack trace has chosen violence.
4
9
 
5
10
  ```text
@@ -70,7 +75,7 @@ An explicit code takes precedence, followed by a recognized name, then a
70
75
  case-sensitive whole-word match in the message. Unknown errors return general
71
76
  guidance with code `UNKNOWN`, or preserve an explicit unknown code.
72
77
  Invalid input throws TypeError; unsupported modes throw RangeError.
73
- Only `plain` is available in this MVP. Humorous modes are planned.
78
+ Modes: `plain` (default) and `rubber-duck`, which preserves the guidance and adds commentary.
74
79
 
75
80
  ### `renderTranslation(result)`
76
81
 
@@ -95,12 +100,12 @@ and stack trace for context. No AI API or remote service is used.
95
100
 
96
101
  `error-translator <code or message> [options]`
97
102
 
98
- | Option | Behavior |
99
- | ----------------- | --------------------- |
100
- | `--help`, `-h` | Show usage |
101
- | `--version`, `-v` | Show package version |
102
- | `--json` | Print structured JSON |
103
- | `--mode plain` | Select plain mode |
103
+ | Option | Behavior |
104
+ | ----------------- | -------------------------------- |
105
+ | `--help`, `-h` | Show usage |
106
+ | `--version`, `-v` | Show package version |
107
+ | `--json` | Print structured JSON |
108
+ | `--mode plain` | Select plain or rubber-duck mode |
104
109
 
105
110
  Quote messages containing spaces or shell punctuation. Use `--` before a message
106
111
  beginning with a dash. No stdin support is included in the MVP.
@@ -141,3 +146,58 @@ are welcome.
141
146
  - [doggo-log](https://github.com/DeployAnyway/doggo-log)
142
147
  - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
143
148
  - [bro-say](https://github.com/DeployAnyway/bro-say)
149
+
150
+ ## Rubber-duck translations
151
+
152
+ Keep the debugging guidance, add workplace-safe rubber-duck commentary. Plain output remains the default.
153
+
154
+ ```sh
155
+ npx @deployanyway/error-translator ECONNREFUSED --mode rubber-duck
156
+ ```
157
+
158
+ API (import the named functions from this package):
159
+
160
+ ```js
161
+ translateError("ENOENT", { mode: "rubber-duck" });
162
+ ```
163
+
164
+ ## Ordered error batches and pipes
165
+
166
+ ```js
167
+ import { translateErrors, listErrors } from "@deployanyway/error-translator";
168
+ const results = translateErrors(["ENOENT", { code: "ECONNREFUSED" }], {
169
+ mode: "rubber-duck",
170
+ });
171
+ console.log(results[0].suggestions);
172
+ console.log(listErrors());
173
+ ```
174
+
175
+ Batches accept 1–100 errors in order, fail on malformed entries and return independent arrays. This is deterministic guidance, not diagnosis. Unknown errors remain unknown: keep the original stack.
176
+
177
+ ```sh
178
+ echo "ECONNREFUSED" | node bin/cli.js --mode rubber-duck
179
+ node bin/cli.js --batch --json < errors.json
180
+ node bin/cli.js --list --json
181
+ ```
182
+
183
+ Without arguments, read UTF-8 stdin (maximum 256 KiB). Arguments take precedence. --batch expects a JSON array; --json emits an array of results. --list catalogs supported codes. Status 0 means translated, including unknown errors; status 2 means invalid input.
184
+
185
+ ## Run from source
186
+
187
+ ```sh
188
+ git clone --branch main https://github.com/DeployAnyway/error-translator.git
189
+ cd error-translator
190
+ npm ci
191
+ npm run build
192
+ node bin/cli.js --help
193
+ ```
194
+
195
+ ## Candidate quality standard
196
+
197
+ Version 0.3 provides useful declaration types, ESM/CommonJS exports, installed-archive checks, and coverage gates (90% statements/lines/functions, 85% branches). CI covers Linux Node 22/24 and Windows/macOS Node 24. Node 22.13+ is required. No runtime dependencies, telemetry or network requests.
198
+
199
+ From a source checkout: npm ci, npm run build, npm run coverage, npm run test:types, npm run verify:package. Pack verification installs a temporary local archive and checks module entries, types, executable and offline npm exec.
200
+
201
+ [Contribution guide](CONTRIBUTING.md) · [Conduct](CODE_OF_CONDUCT.md) · [Security](SECURITY.md) · [Roadmap](ROADMAP.md) · [Migration](MIGRATION.md).
202
+
203
+ **Tools for developers who probably know better.** Software nobody requested, built with questionable priorities, and shipped with absolute confidence!
package/bin/cli.js CHANGED
@@ -1,38 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs } from "node:util";
3
- import { URL } from "node:url";
4
- import { readFileSync } from "node:fs";
5
- import { translateError, renderTranslation } from "../src/index.js";
6
-
7
- try {
8
- const { values, positionals } = parseArgs({
9
- allowPositionals: true,
10
- options: {
11
- help: { type: "boolean", short: "h" },
12
- version: { type: "boolean", short: "v" },
13
- json: { type: "boolean" },
14
- mode: { type: "string", default: "plain" },
15
- },
16
- });
17
- if (values.help) {
18
- console.log(
19
- 'Usage: error-translator <error code or quoted message> [--json] [--mode plain]\n\nTranslate errors into human-readable guidance.\n\nOptions:\n -h, --help Show help\n -v, --version Show version\n --json Print a structured JSON result\n --mode plain Translation mode (MVP: plain only)\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.',
20
- );
21
- } else if (values.version) {
22
- console.log(
23
- JSON.parse(
24
- readFileSync(new URL("../package.json", import.meta.url), "utf8"),
25
- ).version,
26
- );
27
- } else {
28
- const result = translateError(positionals.join(" "), { mode: values.mode });
29
- console.log(
30
- values.json ? JSON.stringify(result, null, 2) : renderTranslation(result),
31
- );
32
- }
33
- } catch (error) {
34
- console.error(
35
- `error-translator: ${error.message}\nRun with --help for usage.`,
36
- );
37
- process.exitCode = 2;
38
- }
2
+ import "../src/cli.js";
package/dist/index.cjs ADDED
@@ -0,0 +1,201 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ listErrors: () => listErrors,
24
+ renderTranslation: () => renderTranslation,
25
+ translateError: () => translateError,
26
+ translateErrors: () => translateErrors
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/definitions.js
31
+ var definitions = {
32
+ ECONNREFUSED: {
33
+ title: "Connection refused",
34
+ explanation: "The application tried to connect to a service, but nothing accepted the connection.",
35
+ likelyCauses: [
36
+ "The service is not running",
37
+ "The host or port is incorrect",
38
+ "A firewall actively rejected the connection"
39
+ ],
40
+ suggestions: [
41
+ "Confirm that the service is running and listening on the configured host and port.",
42
+ "Check connection settings and firewall rules."
43
+ ]
44
+ },
45
+ ENOENT: {
46
+ title: "File or directory not found",
47
+ explanation: "The application tried to use a filesystem path that does not exist.",
48
+ likelyCauses: [
49
+ "The path is misspelled",
50
+ "The file was moved or deleted",
51
+ "A relative path is being resolved from an unexpected working directory"
52
+ ],
53
+ suggestions: [
54
+ "Check the full path and the current working directory.",
55
+ "Create the required file or directory if it should exist."
56
+ ]
57
+ },
58
+ EADDRINUSE: {
59
+ title: "Address already in use",
60
+ explanation: "The application tried to listen on an address and port that another listener already uses.",
61
+ likelyCauses: [
62
+ "Another instance of the application is running",
63
+ "Another service uses the same port"
64
+ ],
65
+ suggestions: [
66
+ "Identify the process using the port before stopping it.",
67
+ "Choose a different available port or stop the duplicate instance."
68
+ ]
69
+ },
70
+ MODULE_NOT_FOUND: {
71
+ title: "Module not found",
72
+ explanation: "Node.js could not resolve a module requested by the application.",
73
+ likelyCauses: [
74
+ "A dependency is missing",
75
+ "The module path is incorrect",
76
+ "A dependency references a missing module"
77
+ ],
78
+ suggestions: [
79
+ "Check the missing module name and the require stack.",
80
+ "Install declared dependencies with npm install and verify local paths."
81
+ ]
82
+ },
83
+ ERR_MODULE_NOT_FOUND: {
84
+ title: "ES module not found",
85
+ explanation: "Node.js could not resolve an imported ES module.",
86
+ likelyCauses: [
87
+ "A dependency is missing",
88
+ "The import path or file extension is incorrect"
89
+ ],
90
+ suggestions: [
91
+ "Install declared dependencies and verify the import path.",
92
+ "Include the file extension for relative Node.js ES module imports."
93
+ ]
94
+ },
95
+ TypeError: {
96
+ title: "Unexpected value type",
97
+ explanation: "An operation received a value it cannot use, such as calling a non-function or reading a property of undefined.",
98
+ likelyCauses: [
99
+ "A value is null or undefined",
100
+ "A value has a different type than expected"
101
+ ],
102
+ suggestions: [
103
+ "Inspect the value at the first relevant line in the stack trace.",
104
+ "Check inputs and return values before accessing properties or calling functions."
105
+ ]
106
+ }
107
+ };
108
+
109
+ // src/index.js
110
+ var listErrors = () => Object.keys(definitions);
111
+ function translateErrors(errors, options = {}) {
112
+ if (!Array.isArray(errors) || errors.length < 1 || errors.length > 100)
113
+ throw new RangeError("Provide 1\u2013100 errors.");
114
+ return Array.from(errors, (error) => translateError(error, options));
115
+ }
116
+ var duckLines = {
117
+ ECONNREFUSED: "Your app knocked. The service has apparently gone for coffee.",
118
+ ENOENT: "The file is playing hide-and-seek. It is currently winning.",
119
+ EADDRINUSE: "Two servers reserved the same chair. Only one gets to sit.",
120
+ MODULE_NOT_FOUND: "The dependency missed roll call. Check its invitation to node_modules.",
121
+ ERR_MODULE_NOT_FOUND: "The module took a wrong turn. Extensions are street signs, not decorations.",
122
+ TypeError: "JavaScript received a surprise guest and forgot how to behave."
123
+ };
124
+ function translateError(error, options = {}) {
125
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
126
+ throw new TypeError("Options must be an object.");
127
+ }
128
+ const mode = options.mode ?? "plain";
129
+ if (!["plain", "rubber-duck"].includes(mode))
130
+ throw new RangeError("Supported modes: plain, rubber-duck.");
131
+ let code;
132
+ let name;
133
+ let message;
134
+ if (typeof error === "string") {
135
+ message = error.trim();
136
+ } else if (error && typeof error === "object" && !Array.isArray(error)) {
137
+ for (const field of ["code", "name", "message"]) {
138
+ if (error[field] !== void 0 && typeof error[field] !== "string") {
139
+ throw new TypeError(`Error ${field} must be a string.`);
140
+ }
141
+ }
142
+ code = error.code?.trim();
143
+ name = error.name?.trim();
144
+ message = error.message?.trim();
145
+ } else {
146
+ throw new TypeError(
147
+ "Provide a nonempty error string or error-like object."
148
+ );
149
+ }
150
+ if (!code && !name && !message)
151
+ throw new TypeError(
152
+ "Provide a nonempty error string or error-like object."
153
+ );
154
+ const matched = code || (Object.hasOwn(definitions, name ?? "") ? name : void 0) || Object.keys(definitions).find(
155
+ (key) => new RegExp(`\\b${key}\\b`).test(message ?? "")
156
+ );
157
+ const definition = Object.hasOwn(definitions, matched ?? "") ? definitions[matched] : void 0;
158
+ const fallback = {
159
+ title: "Unrecognized error",
160
+ explanation: "No built-in translation matches this error yet.",
161
+ likelyCauses: [],
162
+ suggestions: [
163
+ "Read the original error and inspect the first relevant line in its stack trace.",
164
+ "Check the documentation for the component that reported the error."
165
+ ]
166
+ };
167
+ const result = definition ?? fallback;
168
+ return {
169
+ code: matched ?? "UNKNOWN",
170
+ ...result,
171
+ explanation: mode === "rubber-duck" ? `${result.explanation} ${Object.hasOwn(duckLines, matched ?? "") ? duckLines[matched] : "The duck recommends investigating before blaming the compiler."}` : result.explanation,
172
+ likelyCauses: [...result.likelyCauses],
173
+ suggestions: [...result.suggestions],
174
+ mode
175
+ };
176
+ }
177
+ function renderTranslation(result) {
178
+ if (!result || typeof result.title !== "string" || typeof result.explanation !== "string" || !Array.isArray(result.likelyCauses) || !Array.isArray(result.suggestions) || ![...result.likelyCauses, ...result.suggestions].every(
179
+ (item) => typeof item === "string"
180
+ )) {
181
+ throw new TypeError("Provide a structured translation result.");
182
+ }
183
+ return [
184
+ result.title,
185
+ `What happened:
186
+ ${result.explanation}`,
187
+ ...result.likelyCauses.length ? [
188
+ `Likely causes:
189
+ ${result.likelyCauses.map((item) => `- ${item}`).join("\n")}`
190
+ ] : [],
191
+ ...result.suggestions.length ? [`Try:
192
+ ${result.suggestions.map((item) => `- ${item}`).join("\n")}`] : []
193
+ ].join("\n\n");
194
+ }
195
+ // Annotate the CommonJS export names for ESM import in node:
196
+ 0 && (module.exports = {
197
+ listErrors,
198
+ renderTranslation,
199
+ translateError,
200
+ translateErrors
201
+ });
package/index.d.cts ADDED
@@ -0,0 +1,23 @@
1
+ export type ErrorInput =
2
+ string | Error | { code?: string; name?: string; message?: string };
3
+ export interface TranslationOptions {
4
+ mode?: "plain" | "rubber-duck";
5
+ }
6
+ export interface Translation {
7
+ code: string;
8
+ title: string;
9
+ explanation: string;
10
+ likelyCauses: string[];
11
+ suggestions: string[];
12
+ mode: "plain" | "rubber-duck";
13
+ }
14
+ export function translateError(
15
+ error: ErrorInput,
16
+ options?: TranslationOptions,
17
+ ): Translation;
18
+ export function translateErrors(
19
+ errors: ErrorInput[],
20
+ options?: TranslationOptions,
21
+ ): Translation[];
22
+ export function renderTranslation(result: Translation): string;
23
+ export function listErrors(): string[];
package/index.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ export type ErrorInput =
2
+ string | Error | { code?: string; name?: string; message?: string };
3
+ export interface TranslationOptions {
4
+ mode?: "plain" | "rubber-duck";
5
+ }
6
+ export interface Translation {
7
+ code: string;
8
+ title: string;
9
+ explanation: string;
10
+ likelyCauses: string[];
11
+ suggestions: string[];
12
+ mode: "plain" | "rubber-duck";
13
+ }
14
+ export function translateError(
15
+ error: ErrorInput,
16
+ options?: TranslationOptions,
17
+ ): Translation;
18
+ export function translateErrors(
19
+ errors: ErrorInput[],
20
+ options?: TranslationOptions,
21
+ ): Translation[];
22
+ export function renderTranslation(result: Translation): string;
23
+ export function listErrors(): string[];
package/package.json CHANGED
@@ -1,9 +1,20 @@
1
1
  {
2
2
  "name": "@deployanyway/error-translator",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "description": "Plain-English Node.js error explanations and debugging tips, because the stack trace has chosen violence.",
5
5
  "type": "module",
6
- "exports": "./src/index.js",
6
+ "exports": {
7
+ ".": {
8
+ "import": {
9
+ "types": "./index.d.ts",
10
+ "default": "./src/index.js"
11
+ },
12
+ "require": {
13
+ "types": "./index.d.cts",
14
+ "default": "./dist/index.cjs"
15
+ }
16
+ }
17
+ },
7
18
  "bin": {
8
19
  "error-translator": "./bin/cli.js"
9
20
  },
@@ -12,16 +23,26 @@
12
23
  "bin",
13
24
  "README.md",
14
25
  "LICENSE",
15
- "CHANGELOG.md"
26
+ "CHANGELOG.md",
27
+ "dist",
28
+ "index.d.ts",
29
+ "index.d.cts",
30
+ "MIGRATION.md"
16
31
  ],
17
32
  "engines": {
18
- "node": ">=22"
33
+ "node": ">=22.13"
19
34
  },
20
35
  "scripts": {
21
- "test": "node --test",
36
+ "test": "node --test test/*.test.js",
22
37
  "lint": "eslint .",
23
38
  "format": "prettier --write .",
24
- "format:check": "prettier --check ."
39
+ "format:check": "prettier --check .",
40
+ "build": "node scripts/build.mjs",
41
+ "pretest": "npm run build",
42
+ "prepack": "npm run build",
43
+ "coverage": "c8 --all --include=src/**/*.js --reporter=text --reporter=json-summary --reporter=lcov --check-coverage --lines=90 --statements=90 --functions=90 --branches=85 node --test test/*.test.js",
44
+ "test:types": "tsc -p tsconfig.json",
45
+ "verify:package": "node scripts/verify-package.mjs"
25
46
  },
26
47
  "repository": {
27
48
  "type": "git",
@@ -35,8 +56,12 @@
35
56
  "access": "public"
36
57
  },
37
58
  "devDependencies": {
59
+ "@types/node": "^26.6.4",
60
+ "c8": "^12.0.0",
61
+ "esbuild": "^0.28.2",
38
62
  "eslint": "^10.12.0",
39
- "prettier": "^3.6.2"
63
+ "prettier": "^3.6.2",
64
+ "typescript": "^7.0.2"
40
65
  },
41
66
  "keywords": [
42
67
  "nodejs",
@@ -46,6 +71,12 @@
46
71
  "debugging",
47
72
  "error-messages",
48
73
  "error-translator",
49
- "plain-english"
50
- ]
74
+ "plain-english",
75
+ "typescript",
76
+ "humor",
77
+ "deployanyway"
78
+ ],
79
+ "main": "./dist/index.cjs",
80
+ "types": "./index.d.ts",
81
+ "homepage": "https://deployanyway.github.io/"
51
82
  }
package/src/cli.js ADDED
@@ -0,0 +1,59 @@
1
+ import { parseArgs } from "node:util";
2
+ import { URL } from "node:url";
3
+ import { readFileSync } from "node:fs";
4
+ import {
5
+ translateError,
6
+ renderTranslation,
7
+ translateErrors,
8
+ listErrors,
9
+ } from "./index.js";
10
+
11
+ import { readStdin } from "./input.js";
12
+
13
+ try {
14
+ const { values, positionals } = parseArgs({
15
+ allowPositionals: true,
16
+ options: {
17
+ help: { type: "boolean", short: "h" },
18
+ version: { type: "boolean", short: "v" },
19
+ json: { type: "boolean" },
20
+ batch: { type: "boolean" },
21
+ list: { type: "boolean" },
22
+ mode: { type: "string", default: "plain" },
23
+ },
24
+ });
25
+ if (values.help) {
26
+ 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.',
28
+ );
29
+ } else if (values.version) {
30
+ console.log(
31
+ JSON.parse(
32
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
33
+ ).version,
34
+ );
35
+ } else if (values.list) {
36
+ if (positionals.length || values.batch)
37
+ throw new TypeError("--list does not accept input or --batch.");
38
+ console.log(
39
+ values.json ? JSON.stringify(listErrors()) : listErrors().join("\n"),
40
+ );
41
+ } else {
42
+ const text = positionals.length ? positionals.join(" ") : await readStdin();
43
+ const result = values.batch
44
+ ? translateErrors(JSON.parse(text), { mode: values.mode })
45
+ : translateError(text, { mode: values.mode });
46
+ console.log(
47
+ values.json
48
+ ? JSON.stringify(result, null, 2)
49
+ : Array.isArray(result)
50
+ ? result.map(renderTranslation).join("\n\n---\n\n")
51
+ : renderTranslation(result),
52
+ );
53
+ }
54
+ } catch (error) {
55
+ console.error(
56
+ `error-translator: ${error.message}\nRun with --help for usage.`,
57
+ );
58
+ process.exitCode = 2;
59
+ }
package/src/index.js CHANGED
@@ -1,10 +1,28 @@
1
1
  import { definitions } from "./definitions.js";
2
+ export const listErrors = () => Object.keys(definitions);
3
+
4
+ /** Translate an ordered, bounded batch without mutating input. */
5
+ export function translateErrors(errors, options = {}) {
6
+ if (!Array.isArray(errors) || errors.length < 1 || errors.length > 100)
7
+ throw new RangeError("Provide 1–100 errors.");
8
+ return Array.from(errors, (error) => translateError(error, options));
9
+ }
10
+ const duckLines = {
11
+ ECONNREFUSED: "Your app knocked. The service has apparently gone for coffee.",
12
+ ENOENT: "The file is playing hide-and-seek. It is currently winning.",
13
+ EADDRINUSE: "Two servers reserved the same chair. Only one gets to sit.",
14
+ MODULE_NOT_FOUND:
15
+ "The dependency missed roll call. Check its invitation to node_modules.",
16
+ ERR_MODULE_NOT_FOUND:
17
+ "The module took a wrong turn. Extensions are street signs, not decorations.",
18
+ TypeError: "JavaScript received a surprise guest and forgot how to behave.",
19
+ };
2
20
 
3
21
  /**
4
22
  * Translate a nonempty string, Error, or error-like object with a code/name/message.
5
23
  * Explicit codes take precedence over names and message matching.
6
24
  * @param {string | Error | {code?: string, name?: string, message?: string}} error
7
- * @param {{mode?: 'plain'}} [options]
25
+ * @param {{mode?: 'plain' | 'rubber-duck'}} [options]
8
26
  * @returns {{code: string, title: string, explanation: string, likelyCauses: string[], suggestions: string[], mode: string}}
9
27
  */
10
28
  export function translateError(error, options = {}) {
@@ -12,7 +30,8 @@ export function translateError(error, options = {}) {
12
30
  throw new TypeError("Options must be an object.");
13
31
  }
14
32
  const mode = options.mode ?? "plain";
15
- if (mode !== "plain") throw new RangeError("Supported modes: plain.");
33
+ if (!["plain", "rubber-duck"].includes(mode))
34
+ throw new RangeError("Supported modes: plain, rubber-duck.");
16
35
  let code;
17
36
  let name;
18
37
  let message;
@@ -58,6 +77,10 @@ export function translateError(error, options = {}) {
58
77
  return {
59
78
  code: matched ?? "UNKNOWN",
60
79
  ...result,
80
+ explanation:
81
+ mode === "rubber-duck"
82
+ ? `${result.explanation} ${Object.hasOwn(duckLines, matched ?? "") ? duckLines[matched] : "The duck recommends investigating before blaming the compiler."}`
83
+ : result.explanation,
61
84
  likelyCauses: [...result.likelyCauses],
62
85
  suggestions: [...result.suggestions],
63
86
  mode,
package/src/input.js ADDED
@@ -0,0 +1,15 @@
1
+ /** Read bounded UTF-8 stdin without swallowing upstream failures. */
2
+ export async function readStdin(stream = process.stdin) {
3
+ if (stream.isTTY) throw new TypeError("Pipe or redirect input.");
4
+ let size = 0;
5
+ const chunks = [];
6
+ for await (const chunk of stream) {
7
+ const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
8
+ size += buffer.length;
9
+ if (size > 262144) throw new RangeError("stdin exceeds 256 KiB.");
10
+ chunks.push(buffer);
11
+ }
12
+ const text = Buffer.concat(chunks).toString("utf8").trim();
13
+ if (!text) throw new TypeError("Provide nonempty input.");
14
+ return text;
15
+ }