@deployanyway/doggo-log 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 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
  - Scoped logs, optional barks: Use `logger.child("database")` to create an independent logger with a nested prefix. Enable `{ bark: true }` or CLI `--bark` for dog commentary. JSON preserves the original message and adds a separate `commentary` field. Filtering still suppresses output.
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
  # doggo-log
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
  [![npm version](https://img.shields.io/npm/v/%40deployanyway%2Fdoggo-log)](https://www.npmjs.com/package/@deployanyway/doggo-log)
4
6
  [![CI](https://github.com/DeployAnyway/doggo-log/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/DeployAnyway/doggo-log/actions/workflows/ci.yml)
5
7
 
@@ -159,3 +161,42 @@ createDogLogger({ bark: true, prefix: "api" })
159
161
  .child("database")
160
162
  .success("Migration complete");
161
163
  ```
164
+
165
+ ## Give your dog a request ID
166
+
167
+ ```js
168
+ import { createDogLogger } from "@deployanyway/doggo-log";
169
+ const log = createDogLogger({ json: true, context: { service: "api" } });
170
+ const requestLog = log.child("request", { requestId: "abc" });
171
+ requestLog.info("Fetching the evidence.");
172
+ log.withContext({ retry: 1 }).warn("Suspicious squirrel detected.");
173
+ ```
174
+
175
+ JSON records carry a separate context object. Fields must be strings, finite numbers, booleans or null. Configuration is copied; children override fields without mutating parents. Context is intentionally omitted from plain output. Nested data is rejected rather than producing circular or ambiguous logs. Do not log secrets; this is not a redaction framework.
176
+
177
+ ```sh
178
+ echo "Build passed" | node bin/cli.js success --stdin --bark
179
+ node bin/cli.js info "Request accepted" --json --context '{"requestId":"abc"}'
180
+ ```
181
+
182
+ --stdin explicitly reads UTF-8 input up to 256 KiB; message arguments win. Warn/error go to stderr, other methods to stdout. Logging error still exits 0; malformed configuration exits 2. --no-color and NO_COLOR win over --color. PowerShell quoting differs from POSIX shells; use a simple JSON file or the API when shell quoting becomes the least funny part.
183
+
184
+ ## Run from source
185
+
186
+ ```sh
187
+ git clone --branch main https://github.com/DeployAnyway/doggo-log.git
188
+ cd doggo-log
189
+ npm ci
190
+ npm run build
191
+ node bin/cli.js --help
192
+ ```
193
+
194
+ ## Candidate quality standard
195
+
196
+ 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.
197
+
198
+ 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.
199
+
200
+ [Contribution guide](CONTRIBUTING.md) · [Conduct](CODE_OF_CONDUCT.md) · [Security](SECURITY.md) · [Roadmap](ROADMAP.md) · [Migration](MIGRATION.md).
201
+
202
+ **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,53 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import { parseArgs } from "node:util";
3
- import { readFileSync } from "node:fs";
4
- import { URL } from "node:url";
5
- import { createDogLogger } from "../src/index.js";
6
- import { levels } from "../src/levels.js";
7
-
8
- try {
9
- const { values, positionals } = parseArgs({
10
- allowPositionals: true,
11
- options: {
12
- help: { type: "boolean", short: "h" },
13
- version: { type: "boolean", short: "v" },
14
- json: { type: "boolean" },
15
- bark: { type: "boolean" },
16
- timestamp: { type: "boolean" },
17
- quiet: { type: "boolean" },
18
- color: { type: "boolean" },
19
- "no-emoji": { type: "boolean" },
20
- prefix: { type: "string" },
21
- level: { type: "string", default: "debug" },
22
- },
23
- });
24
- if (values.help) {
25
- console.log(
26
- "Usage: doggo-log <method> <message> [options]\n\nMethods: log, info, success, warn, error, debug\nOptions:\n --bark Add useful dog commentary\n --json Emit JSON\n --timestamp Include an ISO timestamp\n --no-emoji Disable emojis\n --color Enable ANSI colors\n --prefix text Add a prefix\n --level name Minimum level (CLI default: debug)\n --quiet Suppress output\n -h, --help Show help\n -v, --version Show version\n\nExit codes: 0 success; 2 invalid arguments. Logging an error exits 0.",
27
- );
28
- } else if (values.version) {
29
- console.log(
30
- JSON.parse(
31
- readFileSync(new URL("../package.json", import.meta.url), "utf8"),
32
- ).version,
33
- );
34
- } else {
35
- const [method, ...message] = positionals;
36
- if (!Object.hasOwn(levels, method ?? "") || !message.join(" ").trim())
37
- throw new TypeError("Provide a valid method and nonempty message.");
38
- const logger = createDogLogger({
39
- emoji: !values["no-emoji"],
40
- bark: values.bark ?? false,
41
- color: values.color ?? false,
42
- timestamp: values.timestamp ?? false,
43
- json: values.json ?? false,
44
- quiet: values.quiet ?? false,
45
- prefix: values.prefix ?? "",
46
- level: values.level,
47
- });
48
- logger[method](message.join(" "));
49
- }
50
- } catch (error) {
51
- console.error(`doggo-log: ${error.message}\nRun with --help for usage.`);
52
- process.exitCode = 2;
53
- }
2
+ import "../src/cli.js";
package/dist/index.cjs ADDED
@@ -0,0 +1,152 @@
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
+ createDogLogger: () => createDogLogger,
24
+ doglog: () => doglog
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_node_util = require("node:util");
28
+
29
+ // src/levels.js
30
+ var levels = {
31
+ debug: { rank: 10, emoji: "\u{1F50D}", color: 90 },
32
+ log: { rank: 20, emoji: "\u{1F436}", color: 37 },
33
+ info: { rank: 20, emoji: "\u{1F436}", color: 36 },
34
+ success: { rank: 20, emoji: "\u{1F43E}", color: 32 },
35
+ warn: { rank: 30, emoji: "\u{1F9B4}", color: 33 },
36
+ error: { rank: 40, emoji: "\u{1F6A8}", color: 31 }
37
+ };
38
+
39
+ // src/index.js
40
+ var barks = {
41
+ debug: "Sniffing for clues.",
42
+ log: "Filed under things I sniffed.",
43
+ info: "Good to know. Good dog to tell you.",
44
+ success: "Treat budget approved.",
45
+ warn: "Suspicious squirrel detected.",
46
+ error: "The dog has fetched the incident report."
47
+ };
48
+ function createDogLogger(options = {}) {
49
+ if (!options || typeof options !== "object" || Array.isArray(options))
50
+ throw new TypeError("Options must be an object.");
51
+ const config = {
52
+ emoji: true,
53
+ bark: false,
54
+ color: false,
55
+ timestamp: false,
56
+ json: false,
57
+ quiet: false,
58
+ prefix: "",
59
+ level: "info",
60
+ context: {},
61
+ write: (line, level) => level === "warn" || level === "error" ? console.error(line) : console.log(line),
62
+ clock: () => /* @__PURE__ */ new Date(),
63
+ ...options
64
+ };
65
+ for (const key of ["emoji", "color", "timestamp", "json", "quiet", "bark"]) {
66
+ if (typeof config[key] !== "boolean")
67
+ throw new TypeError(`${key} must be a boolean.`);
68
+ }
69
+ if (typeof config.prefix !== "string")
70
+ throw new TypeError("prefix must be a string.");
71
+ if (!config.context || typeof config.context !== "object" || Array.isArray(config.context))
72
+ throw new TypeError("context must be an object of scalar fields.");
73
+ for (const value of Object.values(config.context))
74
+ if (!(value === null || typeof value === "string" || typeof value === "boolean" || typeof value === "number" && Number.isFinite(value)))
75
+ throw new TypeError(
76
+ "Context values must be strings, finite numbers, booleans or null."
77
+ );
78
+ config.context = { ...config.context };
79
+ if (typeof config.level !== "string" || !Object.hasOwn(levels, config.level))
80
+ throw new RangeError(
81
+ `level must be one of: ${Object.keys(levels).join(", ")}.`
82
+ );
83
+ if (typeof config.write !== "function" || typeof config.clock !== "function")
84
+ throw new TypeError("write and clock must be functions.");
85
+ const logger = Object.fromEntries(
86
+ Object.entries(levels).map(([level, style]) => [
87
+ level,
88
+ (...args) => {
89
+ if (config.quiet || style.rank < levels[config.level].rank)
90
+ return void 0;
91
+ const message = (0, import_node_util.format)(...args);
92
+ const commentary = config.bark ? barks[level] : void 0;
93
+ let timestamp;
94
+ if (config.timestamp) {
95
+ const date = config.clock();
96
+ if (!(date instanceof Date) || !Number.isFinite(date.getTime()))
97
+ throw new TypeError("clock must return a valid Date.");
98
+ timestamp = date.toISOString();
99
+ }
100
+ let line;
101
+ if (config.json) {
102
+ line = JSON.stringify({
103
+ level,
104
+ message,
105
+ ...Object.keys(config.context).length ? { context: { ...config.context } } : {},
106
+ ...commentary ? { commentary } : {},
107
+ ...config.prefix ? { prefix: config.prefix } : {},
108
+ ...timestamp ? { timestamp } : {}
109
+ });
110
+ } else {
111
+ line = [
112
+ timestamp,
113
+ config.prefix,
114
+ config.emoji ? style.emoji : void 0,
115
+ level.toUpperCase().padEnd(7),
116
+ message,
117
+ commentary
118
+ ].filter((part) => part !== void 0 && part !== "").join(" ");
119
+ if (config.color) line = `\x1B[${style.color}m${line}\x1B[0m`;
120
+ }
121
+ config.write(line, level);
122
+ return line;
123
+ }
124
+ ])
125
+ );
126
+ logger.child = (prefix, context = {}) => {
127
+ if (!context || typeof context !== "object" || Array.isArray(context))
128
+ throw new TypeError("context must be an object.");
129
+ if (typeof prefix !== "string" || !prefix.trim())
130
+ throw new TypeError("Child prefix must be a nonempty string.");
131
+ return createDogLogger({
132
+ ...config,
133
+ context: { ...config.context, ...context },
134
+ prefix: [config.prefix, prefix.trim()].filter(Boolean).join(":")
135
+ });
136
+ };
137
+ logger.withContext = (context) => {
138
+ if (!context || typeof context !== "object" || Array.isArray(context))
139
+ throw new TypeError("context must be an object.");
140
+ return createDogLogger({
141
+ ...config,
142
+ context: { ...config.context, ...context }
143
+ });
144
+ };
145
+ return logger;
146
+ }
147
+ var doglog = createDogLogger();
148
+ // Annotate the CommonJS export names for ESM import in node:
149
+ 0 && (module.exports = {
150
+ createDogLogger,
151
+ doglog
152
+ });
package/index.d.cts ADDED
@@ -0,0 +1,22 @@
1
+ export type Level = "debug" | "log" | "info" | "success" | "warn" | "error";
2
+ export type LogContext = Record<string, string | number | boolean | null>;
3
+ export interface DogOptions {
4
+ emoji?: boolean;
5
+ bark?: boolean;
6
+ color?: boolean;
7
+ timestamp?: boolean;
8
+ json?: boolean;
9
+ quiet?: boolean;
10
+ prefix?: string;
11
+ level?: Level;
12
+ context?: LogContext;
13
+ write?: (line: string, level: Level) => void;
14
+ clock?: () => Date;
15
+ }
16
+ export type LogMethod = (...args: unknown[]) => string | undefined;
17
+ export type DogLogger = Record<Level, LogMethod> & {
18
+ child(prefix: string, context?: LogContext): DogLogger;
19
+ withContext(context: LogContext): DogLogger;
20
+ };
21
+ export function createDogLogger(options?: DogOptions): DogLogger;
22
+ export const doglog: DogLogger;
package/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ export type Level = "debug" | "log" | "info" | "success" | "warn" | "error";
2
+ export type LogContext = Record<string, string | number | boolean | null>;
3
+ export interface DogOptions {
4
+ emoji?: boolean;
5
+ bark?: boolean;
6
+ color?: boolean;
7
+ timestamp?: boolean;
8
+ json?: boolean;
9
+ quiet?: boolean;
10
+ prefix?: string;
11
+ level?: Level;
12
+ context?: LogContext;
13
+ write?: (line: string, level: Level) => void;
14
+ clock?: () => Date;
15
+ }
16
+ export type LogMethod = (...args: unknown[]) => string | undefined;
17
+ export type DogLogger = Record<Level, LogMethod> & {
18
+ child(prefix: string, context?: LogContext): DogLogger;
19
+ withContext(context: LogContext): DogLogger;
20
+ };
21
+ export function createDogLogger(options?: DogOptions): DogLogger;
22
+ export const doglog: DogLogger;
package/package.json CHANGED
@@ -1,9 +1,20 @@
1
1
  {
2
2
  "name": "@deployanyway/doggo-log",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "A tiny Node.js console logger with JSON output, log levels, and dog emojis. Good logs. Very good logs.",
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
  "doggo-log": "./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",
@@ -48,6 +73,12 @@
48
73
  "console",
49
74
  "json",
50
75
  "log-levels",
51
- "dog"
52
- ]
76
+ "dog",
77
+ "typescript",
78
+ "humor",
79
+ "deployanyway"
80
+ ],
81
+ "main": "./dist/index.cjs",
82
+ "types": "./index.d.ts",
83
+ "homepage": "https://deployanyway.github.io/"
53
84
  }
package/src/cli.js ADDED
@@ -0,0 +1,66 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFileSync } from "node:fs";
3
+ import { URL } from "node:url";
4
+ import { createDogLogger } from "./index.js";
5
+ import { levels } from "./levels.js";
6
+
7
+ import { readStdin } from "./input.js";
8
+
9
+ try {
10
+ const { values, positionals } = parseArgs({
11
+ allowPositionals: true,
12
+ options: {
13
+ help: { type: "boolean", short: "h" },
14
+ version: { type: "boolean", short: "v" },
15
+ json: { type: "boolean" },
16
+ stdin: { type: "boolean" },
17
+ context: { type: "string" },
18
+ "no-color": { type: "boolean" },
19
+ bark: { type: "boolean" },
20
+ timestamp: { type: "boolean" },
21
+ quiet: { type: "boolean" },
22
+ color: { type: "boolean" },
23
+ "no-emoji": { type: "boolean" },
24
+ prefix: { type: "string" },
25
+ level: { type: "string", default: "debug" },
26
+ },
27
+ });
28
+ if (values.help) {
29
+ console.log(
30
+ "Usage: doggo-log <method> <message> [options]\n\nMethods: log, info, success, warn, error, debug. --stdin reads a bounded message; arguments win. --context accepts JSON scalar fields for JSON logs; --no-color and NO_COLOR disable ANSI.\nOptions:\n --bark Add useful dog commentary\n --json Emit JSON\n --timestamp Include an ISO timestamp\n --no-emoji Disable emojis\n --color Enable ANSI colors\n --prefix text Add a prefix\n --level name Minimum level (CLI default: debug)\n --quiet Suppress output\n -h, --help Show help\n -v, --version Show version\n\nExit codes: 0 success; 2 invalid arguments. Logging an error exits 0.",
31
+ );
32
+ } else if (values.version) {
33
+ console.log(
34
+ JSON.parse(
35
+ readFileSync(new URL("../package.json", import.meta.url), "utf8"),
36
+ ).version,
37
+ );
38
+ } else {
39
+ const [method, ...message] = positionals;
40
+ const text = message.length
41
+ ? message.join(" ")
42
+ : values.stdin
43
+ ? await readStdin()
44
+ : "";
45
+ if (!Object.hasOwn(levels, method ?? "") || !text.trim())
46
+ throw new TypeError("Provide a valid method and nonempty message.");
47
+ const logger = createDogLogger({
48
+ emoji: !values["no-emoji"],
49
+ bark: values.bark ?? false,
50
+ color:
51
+ !values["no-color"] &&
52
+ !Object.hasOwn(process.env, "NO_COLOR") &&
53
+ (values.color ?? false),
54
+ context: values.context === undefined ? {} : JSON.parse(values.context),
55
+ timestamp: values.timestamp ?? false,
56
+ json: values.json ?? false,
57
+ quiet: values.quiet ?? false,
58
+ prefix: values.prefix ?? "",
59
+ level: values.level,
60
+ });
61
+ logger[method](text);
62
+ }
63
+ } catch (error) {
64
+ console.error(`doggo-log: ${error.message}\nRun with --help for usage.`);
65
+ process.exitCode = 2;
66
+ }
package/src/index.js CHANGED
@@ -25,6 +25,7 @@ export function createDogLogger(options = {}) {
25
25
  quiet: false,
26
26
  prefix: "",
27
27
  level: "info",
28
+ context: {},
28
29
  write: (line, level) =>
29
30
  level === "warn" || level === "error"
30
31
  ? console.error(line)
@@ -38,6 +39,23 @@ export function createDogLogger(options = {}) {
38
39
  }
39
40
  if (typeof config.prefix !== "string")
40
41
  throw new TypeError("prefix must be a string.");
42
+ if (
43
+ !config.context ||
44
+ typeof config.context !== "object" ||
45
+ Array.isArray(config.context)
46
+ )
47
+ throw new TypeError("context must be an object of scalar fields.");
48
+ for (const value of Object.values(config.context))
49
+ if (!(
50
+ value === null ||
51
+ typeof value === "string" ||
52
+ typeof value === "boolean" ||
53
+ (typeof value === "number" && Number.isFinite(value))
54
+ ))
55
+ throw new TypeError(
56
+ "Context values must be strings, finite numbers, booleans or null.",
57
+ );
58
+ config.context = { ...config.context };
41
59
  if (typeof config.level !== "string" || !Object.hasOwn(levels, config.level))
42
60
  throw new RangeError(
43
61
  `level must be one of: ${Object.keys(levels).join(", ")}.`,
@@ -64,6 +82,9 @@ export function createDogLogger(options = {}) {
64
82
  line = JSON.stringify({
65
83
  level,
66
84
  message,
85
+ ...(Object.keys(config.context).length
86
+ ? { context: { ...config.context } }
87
+ : {}),
67
88
  ...(commentary ? { commentary } : {}),
68
89
  ...(config.prefix ? { prefix: config.prefix } : {}),
69
90
  ...(timestamp ? { timestamp } : {}),
@@ -86,14 +107,25 @@ export function createDogLogger(options = {}) {
86
107
  },
87
108
  ]),
88
109
  );
89
- logger.child = (prefix) => {
110
+ logger.child = (prefix, context = {}) => {
111
+ if (!context || typeof context !== "object" || Array.isArray(context))
112
+ throw new TypeError("context must be an object.");
90
113
  if (typeof prefix !== "string" || !prefix.trim())
91
114
  throw new TypeError("Child prefix must be a nonempty string.");
92
115
  return createDogLogger({
93
116
  ...config,
117
+ context: { ...config.context, ...context },
94
118
  prefix: [config.prefix, prefix.trim()].filter(Boolean).join(":"),
95
119
  });
96
120
  };
121
+ logger.withContext = (context) => {
122
+ if (!context || typeof context !== "object" || Array.isArray(context))
123
+ throw new TypeError("context must be an object.");
124
+ return createDogLogger({
125
+ ...config,
126
+ context: { ...config.context, ...context },
127
+ });
128
+ };
97
129
  return logger;
98
130
  }
99
131
 
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
+ }