@deployanyway/doggo-log 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
+ - 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.
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
  # 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
+
5
+ [![npm version](https://img.shields.io/npm/v/%40deployanyway%2Fdoggo-log)](https://www.npmjs.com/package/@deployanyway/doggo-log)
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)
7
+
3
8
  A tiny Node.js console logger with JSON output, log levels, and dog emojis. Good logs. Very good logs.
4
9
 
5
10
  ```text
@@ -140,3 +145,58 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
140
145
  - [doggo-log](https://github.com/DeployAnyway/doggo-log)
141
146
  - [ship-it-meter](https://github.com/DeployAnyway/ship-it-meter)
142
147
  - [bro-say](https://github.com/DeployAnyway/bro-say)
148
+
149
+ ## Scoped logs, optional barks
150
+
151
+ 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.
152
+
153
+ ```sh
154
+ npx @deployanyway/doggo-log success "Migration complete" --bark --prefix database
155
+ ```
156
+
157
+ API (import the named functions from this package):
158
+
159
+ ```js
160
+ createDogLogger({ bark: true, prefix: "api" })
161
+ .child("database")
162
+ .success("Migration complete");
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,51 +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
- timestamp: { type: "boolean" },
16
- quiet: { type: "boolean" },
17
- color: { type: "boolean" },
18
- "no-emoji": { type: "boolean" },
19
- prefix: { type: "string" },
20
- level: { type: "string", default: "debug" },
21
- },
22
- });
23
- if (values.help) {
24
- console.log(
25
- "Usage: doggo-log <method> <message> [options]\n\nMethods: log, info, success, warn, error, debug\nOptions:\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.",
26
- );
27
- } else if (values.version) {
28
- console.log(
29
- JSON.parse(
30
- readFileSync(new URL("../package.json", import.meta.url), "utf8"),
31
- ).version,
32
- );
33
- } else {
34
- const [method, ...message] = positionals;
35
- if (!Object.hasOwn(levels, method ?? "") || !message.join(" ").trim())
36
- throw new TypeError("Provide a valid method and nonempty message.");
37
- const logger = createDogLogger({
38
- emoji: !values["no-emoji"],
39
- color: values.color ?? false,
40
- timestamp: values.timestamp ?? false,
41
- json: values.json ?? false,
42
- quiet: values.quiet ?? false,
43
- prefix: values.prefix ?? "",
44
- level: values.level,
45
- });
46
- logger[method](message.join(" "));
47
- }
48
- } catch (error) {
49
- console.error(`doggo-log: ${error.message}\nRun with --help for usage.`);
50
- process.exitCode = 2;
51
- }
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.1.1",
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
@@ -1,5 +1,13 @@
1
1
  import { format } from "node:util";
2
2
  import { levels } from "./levels.js";
3
+ const barks = {
4
+ debug: "Sniffing for clues.",
5
+ log: "Filed under things I sniffed.",
6
+ info: "Good to know. Good dog to tell you.",
7
+ success: "Treat budget approved.",
8
+ warn: "Suspicious squirrel detected.",
9
+ error: "The dog has fetched the incident report.",
10
+ };
3
11
 
4
12
  /**
5
13
  * Create a small logger. Methods return the emitted line, or undefined if filtered.
@@ -10,12 +18,14 @@ export function createDogLogger(options = {}) {
10
18
  throw new TypeError("Options must be an object.");
11
19
  const config = {
12
20
  emoji: true,
21
+ bark: false,
13
22
  color: false,
14
23
  timestamp: false,
15
24
  json: false,
16
25
  quiet: false,
17
26
  prefix: "",
18
27
  level: "info",
28
+ context: {},
19
29
  write: (line, level) =>
20
30
  level === "warn" || level === "error"
21
31
  ? console.error(line)
@@ -23,25 +33,43 @@ export function createDogLogger(options = {}) {
23
33
  clock: () => new Date(),
24
34
  ...options,
25
35
  };
26
- for (const key of ["emoji", "color", "timestamp", "json", "quiet"]) {
36
+ for (const key of ["emoji", "color", "timestamp", "json", "quiet", "bark"]) {
27
37
  if (typeof config[key] !== "boolean")
28
38
  throw new TypeError(`${key} must be a boolean.`);
29
39
  }
30
40
  if (typeof config.prefix !== "string")
31
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 };
32
59
  if (typeof config.level !== "string" || !Object.hasOwn(levels, config.level))
33
60
  throw new RangeError(
34
61
  `level must be one of: ${Object.keys(levels).join(", ")}.`,
35
62
  );
36
63
  if (typeof config.write !== "function" || typeof config.clock !== "function")
37
64
  throw new TypeError("write and clock must be functions.");
38
- return Object.fromEntries(
65
+ const logger = Object.fromEntries(
39
66
  Object.entries(levels).map(([level, style]) => [
40
67
  level,
41
68
  (...args) => {
42
69
  if (config.quiet || style.rank < levels[config.level].rank)
43
70
  return undefined;
44
71
  const message = format(...args);
72
+ const commentary = config.bark ? barks[level] : undefined;
45
73
  let timestamp;
46
74
  if (config.timestamp) {
47
75
  const date = config.clock();
@@ -54,6 +82,10 @@ export function createDogLogger(options = {}) {
54
82
  line = JSON.stringify({
55
83
  level,
56
84
  message,
85
+ ...(Object.keys(config.context).length
86
+ ? { context: { ...config.context } }
87
+ : {}),
88
+ ...(commentary ? { commentary } : {}),
57
89
  ...(config.prefix ? { prefix: config.prefix } : {}),
58
90
  ...(timestamp ? { timestamp } : {}),
59
91
  });
@@ -64,6 +96,7 @@ export function createDogLogger(options = {}) {
64
96
  config.emoji ? style.emoji : undefined,
65
97
  level.toUpperCase().padEnd(7),
66
98
  message,
99
+ commentary,
67
100
  ]
68
101
  .filter((part) => part !== undefined && part !== "")
69
102
  .join(" ");
@@ -74,6 +107,26 @@ export function createDogLogger(options = {}) {
74
107
  },
75
108
  ]),
76
109
  );
110
+ logger.child = (prefix, context = {}) => {
111
+ if (!context || typeof context !== "object" || Array.isArray(context))
112
+ throw new TypeError("context must be an object.");
113
+ if (typeof prefix !== "string" || !prefix.trim())
114
+ throw new TypeError("Child prefix must be a nonempty string.");
115
+ return createDogLogger({
116
+ ...config,
117
+ context: { ...config.context, ...context },
118
+ prefix: [config.prefix, prefix.trim()].filter(Boolean).join(":"),
119
+ });
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
+ };
129
+ return logger;
77
130
  }
78
131
 
79
132
  /** Default logger: emoji enabled, no timestamp/color, info threshold. */
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
+ }