@deployanyway/error-translator 0.2.0 → 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 +6 -0
- package/MIGRATION.md +5 -0
- package/README.md +43 -0
- package/bin/cli.js +1 -37
- package/dist/index.cjs +201 -0
- package/index.d.cts +23 -0
- package/index.d.ts +23 -0
- package/package.json +40 -9
- package/src/cli.js +59 -0
- package/src/index.js +8 -0
- package/src/input.js +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
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
|
+
|
|
3
9
|
## 0.2.0 — 2026-10-08
|
|
4
10
|
|
|
5
11
|
- Rubber-duck translations: Keep the debugging guidance, add workplace-safe rubber-duck commentary. Plain output remains the default.
|
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,7 @@
|
|
|
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
|
+
|
|
3
5
|
[](https://www.npmjs.com/package/@deployanyway/error-translator)
|
|
4
6
|
[](https://github.com/DeployAnyway/error-translator/actions/workflows/ci.yml)
|
|
5
7
|
|
|
@@ -158,3 +160,44 @@ API (import the named functions from this package):
|
|
|
158
160
|
```js
|
|
159
161
|
translateError("ENOENT", { mode: "rubber-duck" });
|
|
160
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
|
|
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|rubber-duck]\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|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.',
|
|
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.
|
|
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":
|
|
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,4 +1,12 @@
|
|
|
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
|
+
}
|
|
2
10
|
const duckLines = {
|
|
3
11
|
ECONNREFUSED: "Your app knocked. The service has apparently gone for coffee.",
|
|
4
12
|
ENOENT: "The file is playing hide-and-seek. It is currently winning.",
|
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
|
+
}
|