@lahin31/debugcontext-core 0.1.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/README.md +37 -0
- package/dist/index.cjs +353 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +332 -0
- package/dist/index.d.ts +332 -0
- package/dist/index.js +329 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
import { mkdirSync, appendFileSync, existsSync, readFileSync } from 'fs';
|
|
3
|
+
import { resolve, dirname, join } from 'path';
|
|
4
|
+
import os2 from 'os';
|
|
5
|
+
import { randomUUID } from 'crypto';
|
|
6
|
+
|
|
7
|
+
// src/collectors/error.ts
|
|
8
|
+
function collectError(thrown) {
|
|
9
|
+
if (thrown instanceof Error) {
|
|
10
|
+
return {
|
|
11
|
+
name: thrown.name,
|
|
12
|
+
message: thrown.message,
|
|
13
|
+
stack: thrown.stack,
|
|
14
|
+
cause: serialiseCause(thrown.cause)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
name: "UnknownError",
|
|
19
|
+
message: String(thrown),
|
|
20
|
+
stack: void 0,
|
|
21
|
+
cause: void 0
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function serialiseCause(cause) {
|
|
25
|
+
if (cause === void 0 || cause === null) return void 0;
|
|
26
|
+
if (cause instanceof Error) {
|
|
27
|
+
return {
|
|
28
|
+
name: cause.name,
|
|
29
|
+
message: cause.message,
|
|
30
|
+
stack: cause.stack,
|
|
31
|
+
cause: serialiseCause(cause.cause)
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(JSON.stringify(cause));
|
|
36
|
+
} catch {
|
|
37
|
+
return String(cause);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function runGit(args) {
|
|
41
|
+
try {
|
|
42
|
+
return execSync(`git ${args}`, {
|
|
43
|
+
encoding: "utf8",
|
|
44
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
45
|
+
timeout: 2e3
|
|
46
|
+
}).trim();
|
|
47
|
+
} catch {
|
|
48
|
+
return "unknown";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function resolvePackageVersion() {
|
|
52
|
+
let dir = process.cwd();
|
|
53
|
+
for (let i = 0; i < 10; i++) {
|
|
54
|
+
const candidate = join(dir, "package.json");
|
|
55
|
+
if (existsSync(candidate)) {
|
|
56
|
+
try {
|
|
57
|
+
const raw = readFileSync(candidate, "utf8");
|
|
58
|
+
const pkg = JSON.parse(raw);
|
|
59
|
+
if (typeof pkg.version === "string" && pkg.version) {
|
|
60
|
+
return pkg.version;
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const parent = join(dir, "..");
|
|
66
|
+
if (parent === dir) break;
|
|
67
|
+
dir = parent;
|
|
68
|
+
}
|
|
69
|
+
return "unknown";
|
|
70
|
+
}
|
|
71
|
+
var cachedGitContext = null;
|
|
72
|
+
function collectGit() {
|
|
73
|
+
if (cachedGitContext !== null) return cachedGitContext;
|
|
74
|
+
cachedGitContext = {
|
|
75
|
+
commitHash: runGit("rev-parse --short=8 HEAD"),
|
|
76
|
+
branch: runGit("rev-parse --abbrev-ref HEAD"),
|
|
77
|
+
packageVersion: resolvePackageVersion()
|
|
78
|
+
};
|
|
79
|
+
return cachedGitContext;
|
|
80
|
+
}
|
|
81
|
+
function collectRuntime() {
|
|
82
|
+
return {
|
|
83
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
84
|
+
environment: process.env["NODE_ENV"] ?? "development",
|
|
85
|
+
nodeVersion: process.version,
|
|
86
|
+
pid: process.pid,
|
|
87
|
+
hostname: os2.hostname(),
|
|
88
|
+
uptimeSeconds: process.uptime(),
|
|
89
|
+
workingDirectory: process.cwd()
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function collectSystem() {
|
|
93
|
+
const mem = process.memoryUsage();
|
|
94
|
+
const heapUsagePercent = mem.heapTotal > 0 ? Math.round(mem.heapUsed / mem.heapTotal * 100 * 10) / 10 : 0;
|
|
95
|
+
const [one, five, fifteen] = os2.loadavg();
|
|
96
|
+
return {
|
|
97
|
+
memory: {
|
|
98
|
+
rss: mem.rss,
|
|
99
|
+
heapTotal: mem.heapTotal,
|
|
100
|
+
heapUsed: mem.heapUsed,
|
|
101
|
+
external: mem.external,
|
|
102
|
+
arrayBuffers: mem.arrayBuffers
|
|
103
|
+
},
|
|
104
|
+
heapUsagePercent,
|
|
105
|
+
cpuLoadAvg: [one, five, fifteen],
|
|
106
|
+
platform: os2.platform(),
|
|
107
|
+
arch: os2.arch(),
|
|
108
|
+
totalMemory: os2.totalmem(),
|
|
109
|
+
freeMemory: os2.freemem()
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function generateId() {
|
|
113
|
+
return randomUUID();
|
|
114
|
+
}
|
|
115
|
+
function toFile(incident, options2 = {}) {
|
|
116
|
+
const filePath = resolve(process.cwd(), options2.path ?? "incidents.ndjson");
|
|
117
|
+
const mkdirp = options2.mkdirp ?? true;
|
|
118
|
+
if (mkdirp) {
|
|
119
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
120
|
+
}
|
|
121
|
+
const line = JSON.stringify(incident) + "\n";
|
|
122
|
+
appendFileSync(filePath, line, "utf8");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/debug-context.ts
|
|
126
|
+
var options = buildDefaultOptions();
|
|
127
|
+
var lastIncident = null;
|
|
128
|
+
var globalHandlersAttached = false;
|
|
129
|
+
function init(userOptions = {}) {
|
|
130
|
+
options = {
|
|
131
|
+
...buildDefaultOptions(),
|
|
132
|
+
...userOptions,
|
|
133
|
+
sensitiveHeaders: [
|
|
134
|
+
...userOptions.sensitiveHeaders ?? []
|
|
135
|
+
],
|
|
136
|
+
sensitiveFields: [
|
|
137
|
+
...userOptions.sensitiveFields ?? []
|
|
138
|
+
]
|
|
139
|
+
};
|
|
140
|
+
collectGit();
|
|
141
|
+
if (options.captureGlobalErrors && !globalHandlersAttached) {
|
|
142
|
+
attachGlobalHandlers();
|
|
143
|
+
globalHandlersAttached = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function capture(error, request = null) {
|
|
147
|
+
const incident = {
|
|
148
|
+
incidentId: generateId(),
|
|
149
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
150
|
+
request,
|
|
151
|
+
runtime: collectRuntime(),
|
|
152
|
+
system: collectSystem(),
|
|
153
|
+
git: collectGit(),
|
|
154
|
+
error: collectError(error)
|
|
155
|
+
};
|
|
156
|
+
lastIncident = incident;
|
|
157
|
+
if (options.onIncident) {
|
|
158
|
+
Promise.resolve(options.onIncident(incident)).catch((hookErr) => {
|
|
159
|
+
console.error("[DebugContext] onIncident hook threw:", hookErr);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
return incident;
|
|
163
|
+
}
|
|
164
|
+
function toJSON(incident) {
|
|
165
|
+
const target = incident ?? lastIncident;
|
|
166
|
+
if (!target) return null;
|
|
167
|
+
return JSON.stringify(target, null, 2);
|
|
168
|
+
}
|
|
169
|
+
function toConsole(incident) {
|
|
170
|
+
const target = incident ?? lastIncident;
|
|
171
|
+
if (!target) {
|
|
172
|
+
console.warn("[DebugContext] No incident to display.");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const hr = "\u2500".repeat(60);
|
|
176
|
+
console.error(`
|
|
177
|
+
${hr}
|
|
178
|
+
\u{1F41B} DebugContext Incident ${target.incidentId}
|
|
179
|
+
${hr}
|
|
180
|
+
|
|
181
|
+
Error : ${target.error.name}: ${target.error.message}
|
|
182
|
+
Timestamp : ${target.timestamp}
|
|
183
|
+
Environment: ${target.runtime.environment}
|
|
184
|
+
Node : ${target.runtime.nodeVersion}
|
|
185
|
+
PID : ${target.runtime.pid}
|
|
186
|
+
Hostname : ${target.runtime.hostname}
|
|
187
|
+
Uptime : ${target.runtime.uptimeSeconds.toFixed(1)}s
|
|
188
|
+
Commit : ${target.git.commitHash} (${target.git.branch})
|
|
189
|
+
Version : ${target.git.packageVersion}
|
|
190
|
+
|
|
191
|
+
Heap : ${target.system.heapUsagePercent}% used
|
|
192
|
+
RSS : ${formatBytes(target.system.memory.rss)}
|
|
193
|
+
|
|
194
|
+
${target.request ? formatRequest(target.request) : " No request context."}
|
|
195
|
+
|
|
196
|
+
Stack:
|
|
197
|
+
${indentStack(target.error.stack ?? "(no stack)")}
|
|
198
|
+
${hr}
|
|
199
|
+
`);
|
|
200
|
+
}
|
|
201
|
+
function toFile2(incident, options2) {
|
|
202
|
+
const target = incident ?? lastIncident;
|
|
203
|
+
if (!target) {
|
|
204
|
+
console.warn("[DebugContext] No incident to write.");
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
toFile(target, options2);
|
|
208
|
+
}
|
|
209
|
+
function middleware() {
|
|
210
|
+
return (error, request = null) => {
|
|
211
|
+
return capture(error, request);
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
function getOptions() {
|
|
215
|
+
return options;
|
|
216
|
+
}
|
|
217
|
+
function attachGlobalHandlers() {
|
|
218
|
+
process.on("uncaughtException", (err) => {
|
|
219
|
+
const incident = capture(err, null);
|
|
220
|
+
toConsole(incident);
|
|
221
|
+
process.exitCode = 1;
|
|
222
|
+
});
|
|
223
|
+
process.on("unhandledRejection", (reason) => {
|
|
224
|
+
capture(reason, null);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
function formatRequest(req) {
|
|
228
|
+
return [
|
|
229
|
+
` Request:`,
|
|
230
|
+
` ${req.method} ${req.url}`,
|
|
231
|
+
` IP : ${req.ip}`,
|
|
232
|
+
` User-Agent: ${req.userAgent}`
|
|
233
|
+
].join("\n");
|
|
234
|
+
}
|
|
235
|
+
function formatBytes(bytes) {
|
|
236
|
+
const mb = bytes / 1024 / 1024;
|
|
237
|
+
return `${mb.toFixed(1)} MB`;
|
|
238
|
+
}
|
|
239
|
+
function indentStack(stack) {
|
|
240
|
+
return stack.split("\n").map((l) => ` ${l}`).join("\n");
|
|
241
|
+
}
|
|
242
|
+
function buildDefaultOptions() {
|
|
243
|
+
return {
|
|
244
|
+
sensitiveHeaders: [],
|
|
245
|
+
sensitiveFields: [],
|
|
246
|
+
onIncident: () => void 0,
|
|
247
|
+
captureGlobalErrors: true
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
var DebugContext = {
|
|
251
|
+
init,
|
|
252
|
+
capture,
|
|
253
|
+
toJSON,
|
|
254
|
+
toConsole,
|
|
255
|
+
toFile: toFile2,
|
|
256
|
+
middleware,
|
|
257
|
+
/** @internal used by framework adapters */
|
|
258
|
+
getOptions
|
|
259
|
+
};
|
|
260
|
+
var debug_context_default = DebugContext;
|
|
261
|
+
|
|
262
|
+
// src/utils/redact.ts
|
|
263
|
+
var DEFAULT_SENSITIVE_HEADERS = [
|
|
264
|
+
"authorization",
|
|
265
|
+
"cookie",
|
|
266
|
+
"set-cookie",
|
|
267
|
+
"x-api-key",
|
|
268
|
+
"x-auth-token",
|
|
269
|
+
"x-access-token",
|
|
270
|
+
"proxy-authorization",
|
|
271
|
+
"www-authenticate"
|
|
272
|
+
];
|
|
273
|
+
var DEFAULT_SENSITIVE_FIELDS = [
|
|
274
|
+
"password",
|
|
275
|
+
"passwd",
|
|
276
|
+
"secret",
|
|
277
|
+
"token",
|
|
278
|
+
"apikey",
|
|
279
|
+
"api_key",
|
|
280
|
+
"accesstoken",
|
|
281
|
+
"access_token",
|
|
282
|
+
"refreshtoken",
|
|
283
|
+
"refresh_token",
|
|
284
|
+
"creditcard",
|
|
285
|
+
"credit_card",
|
|
286
|
+
"cardnumber",
|
|
287
|
+
"card_number",
|
|
288
|
+
"cvv",
|
|
289
|
+
"ssn",
|
|
290
|
+
"privatekey",
|
|
291
|
+
"private_key"
|
|
292
|
+
];
|
|
293
|
+
var REDACTED = "[REDACTED]";
|
|
294
|
+
function redactHeaders(headers, extraFields = []) {
|
|
295
|
+
const sensitiveSet = new Set(
|
|
296
|
+
[...DEFAULT_SENSITIVE_HEADERS, ...extraFields].map((h) => h.toLowerCase())
|
|
297
|
+
);
|
|
298
|
+
const result = {};
|
|
299
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
300
|
+
if (value === void 0) continue;
|
|
301
|
+
const normalised = key.toLowerCase();
|
|
302
|
+
const raw = Array.isArray(value) ? value.join(", ") : value;
|
|
303
|
+
result[key] = sensitiveSet.has(normalised) ? REDACTED : raw;
|
|
304
|
+
}
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
function redactBody(body, extraFields = []) {
|
|
308
|
+
const sensitiveSet = new Set(
|
|
309
|
+
[...DEFAULT_SENSITIVE_FIELDS, ...extraFields].map((f) => f.toLowerCase())
|
|
310
|
+
);
|
|
311
|
+
return walk(body, sensitiveSet, 0);
|
|
312
|
+
}
|
|
313
|
+
var MAX_DEPTH = 10;
|
|
314
|
+
function walk(value, sensitive, depth) {
|
|
315
|
+
if (depth > MAX_DEPTH) return "[MAX_DEPTH_EXCEEDED]";
|
|
316
|
+
if (value === null || typeof value !== "object") return value;
|
|
317
|
+
if (Array.isArray(value)) {
|
|
318
|
+
return value.map((item) => walk(item, sensitive, depth + 1));
|
|
319
|
+
}
|
|
320
|
+
const result = {};
|
|
321
|
+
for (const [key, val] of Object.entries(value)) {
|
|
322
|
+
result[key] = sensitive.has(key.toLowerCase()) ? REDACTED : walk(val, sensitive, depth + 1);
|
|
323
|
+
}
|
|
324
|
+
return result;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export { DEFAULT_SENSITIVE_FIELDS, DEFAULT_SENSITIVE_HEADERS, capture, collectError, collectGit, collectRuntime, collectSystem, debug_context_default as default, getOptions, init, middleware, redactBody, redactHeaders, toConsole, toFile2 as toFile, toJSON, toFile as writeIncidentToFile };
|
|
328
|
+
//# sourceMappingURL=index.js.map
|
|
329
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/collectors/error.ts","../src/collectors/git.ts","../src/collectors/runtime.ts","../src/collectors/system.ts","../src/utils/id.ts","../src/utils/to-file.ts","../src/debug-context.ts","../src/utils/redact.ts"],"names":["os","options","toFile"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @module collectors/error\n * Serialises a thrown value into a structured ErrorContext.\n */\nimport type { ErrorContext } from \"../types.js\";\n\n/**\n * Converts any thrown value into a structured, serialisable ErrorContext.\n *\n * Handles:\n * - Standard `Error` instances (including subclasses).\n * - Errors with a `.cause` (ES2022 error chaining).\n * - Non-Error throws (strings, objects, etc.).\n */\nexport function collectError(thrown: unknown): ErrorContext {\n if (thrown instanceof Error) {\n return {\n name: thrown.name,\n message: thrown.message,\n stack: thrown.stack,\n cause: serialiseCause(thrown.cause),\n };\n }\n\n // Non-Error throw — wrap it so the shape is always consistent\n return {\n name: \"UnknownError\",\n message: String(thrown),\n stack: undefined,\n cause: undefined,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction serialiseCause(cause: unknown): unknown {\n if (cause === undefined || cause === null) return undefined;\n\n if (cause instanceof Error) {\n return {\n name: cause.name,\n message: cause.message,\n stack: cause.stack,\n cause: serialiseCause(cause.cause),\n };\n }\n\n // Primitive or plain object cause\n try {\n // Ensure it's JSON-safe\n return JSON.parse(JSON.stringify(cause)) as unknown;\n } catch {\n return String(cause);\n }\n}\n","/**\n * @module collectors/git\n * Resolves Git metadata and package version at SDK initialisation time.\n *\n * All resolution is synchronous and happens once — results are cached so\n * repeated incident captures don't spawn child processes.\n */\nimport { execSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { GitContext } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction runGit(args: string): string {\n try {\n return execSync(`git ${args}`, {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 2000,\n }).trim();\n } catch {\n return \"unknown\";\n }\n}\n\nfunction resolvePackageVersion(): string {\n let dir = process.cwd();\n\n // Walk up the directory tree looking for package.json\n for (let i = 0; i < 10; i++) {\n const candidate = join(dir, \"package.json\");\n if (existsSync(candidate)) {\n try {\n const raw = readFileSync(candidate, \"utf8\");\n const pkg = JSON.parse(raw) as { version?: unknown };\n if (typeof pkg.version === \"string\" && pkg.version) {\n return pkg.version;\n }\n } catch {\n // malformed package.json — keep walking\n }\n }\n const parent = join(dir, \"..\");\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n return \"unknown\";\n}\n\n// ---------------------------------------------------------------------------\n// Cached resolution\n// ---------------------------------------------------------------------------\n\nlet cachedGitContext: GitContext | null = null;\n\n/**\n * Returns the Git context. Resolution runs once and is then cached for the\n * lifetime of the process.\n */\nexport function collectGit(): GitContext {\n if (cachedGitContext !== null) return cachedGitContext;\n\n cachedGitContext = {\n commitHash: runGit(\"rev-parse --short=8 HEAD\"),\n branch: runGit(\"rev-parse --abbrev-ref HEAD\"),\n packageVersion: resolvePackageVersion(),\n };\n\n return cachedGitContext;\n}\n\n/**\n * Clears the cached Git context. Useful in tests.\n * @internal\n */\nexport function _clearGitCache(): void {\n cachedGitContext = null;\n}\n","/**\n * @module collectors/runtime\n * Collects Node.js runtime information.\n */\nimport os from \"node:os\";\nimport type { RuntimeContext } from \"../types.js\";\n\n/**\n * Captures a snapshot of the current Node.js runtime environment.\n */\nexport function collectRuntime(): RuntimeContext {\n return {\n timestamp: new Date().toISOString(),\n environment: process.env[\"NODE_ENV\"] ?? \"development\",\n nodeVersion: process.version,\n pid: process.pid,\n hostname: os.hostname(),\n uptimeSeconds: process.uptime(),\n workingDirectory: process.cwd(),\n };\n}\n","/**\n * @module collectors/system\n * Collects OS / process resource usage.\n */\nimport os from \"node:os\";\nimport type { SystemContext } from \"../types.js\";\n\n/**\n * Captures a snapshot of current system resource usage.\n */\nexport function collectSystem(): SystemContext {\n const mem = process.memoryUsage();\n const heapUsagePercent =\n mem.heapTotal > 0\n ? Math.round((mem.heapUsed / mem.heapTotal) * 100 * 10) / 10\n : 0;\n\n const [one, five, fifteen] = os.loadavg() as [number, number, number];\n\n return {\n memory: {\n rss: mem.rss,\n heapTotal: mem.heapTotal,\n heapUsed: mem.heapUsed,\n external: mem.external,\n arrayBuffers: mem.arrayBuffers,\n },\n heapUsagePercent,\n cpuLoadAvg: [one, five, fifteen],\n platform: os.platform(),\n arch: os.arch(),\n totalMemory: os.totalmem(),\n freeMemory: os.freemem(),\n };\n}\n","/**\n * Lightweight UUID v4 generator that relies only on Node's built-in\n * `crypto.randomUUID()` (Node ≥ 14.17) — no external dependencies.\n */\nimport { randomUUID } from \"node:crypto\";\n\n/** Returns a new UUID v4 string. */\nexport function generateId(): string {\n return randomUUID();\n}\n","/**\n * @module utils/to-file\n * Writes incidents to a newline-delimited JSON (NDJSON) file.\n *\n * NDJSON is the standard format for append-only incident logs — one JSON\n * object per line, easy to stream, grep, and parse with any tool.\n *\n * @example\n * ```ts\n * import DebugContext from '@debugcontext/core';\n *\n * DebugContext.init({\n * onIncident: (incident) => DebugContext.toFile(incident),\n * });\n * ```\n */\nimport { appendFileSync, mkdirSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport type { Incident } from \"../types.js\";\n\nexport interface ToFileOptions {\n /**\n * Path to the output file.\n * @default \"incidents.ndjson\" (relative to process.cwd())\n */\n path?: string;\n /**\n * When `true`, creates parent directories if they don't exist.\n * @default true\n */\n mkdirp?: boolean;\n}\n\n/**\n * Appends an incident as a single JSON line to an NDJSON file.\n *\n * - Safe to call from multiple processes (append is atomic on most OSes).\n * - Synchronous by design so it can be called inside `onIncident` without\n * worrying about buffering or unhandled promise rejections.\n *\n * @param incident - The incident to write.\n * @param options - Output options.\n *\n * @example\n * ```ts\n * // Write to the default path (incidents.ndjson)\n * DebugContext.toFile(incident);\n *\n * // Write to a custom path\n * DebugContext.toFile(incident, { path: 'logs/incidents.ndjson' });\n * ```\n */\nexport function toFile(\n incident: Incident,\n options: ToFileOptions = {}\n): void {\n const filePath = resolve(process.cwd(), options.path ?? \"incidents.ndjson\");\n const mkdirp = options.mkdirp ?? true;\n\n if (mkdirp) {\n mkdirSync(dirname(filePath), { recursive: true });\n }\n\n const line = JSON.stringify(incident) + \"\\n\";\n appendFileSync(filePath, line, \"utf8\");\n}\n","/**\n * @module debug-context\n * Core DebugContext singleton — orchestrates collectors and exposes the\n * public SDK API.\n */\nimport { collectError } from \"./collectors/error.js\";\nimport { collectGit } from \"./collectors/git.js\";\nimport { collectRuntime } from \"./collectors/runtime.js\";\nimport { collectSystem } from \"./collectors/system.js\";\nimport type {\n DebugContextOptions,\n Incident,\n RequestContext,\n} from \"./types.js\";\nimport { generateId } from \"./utils/id.js\";\nimport { toFile as toFileUtil, type ToFileOptions } from \"./utils/to-file.js\";\n\n// ---------------------------------------------------------------------------\n// Internal state\n// ---------------------------------------------------------------------------\n\nlet options: Required<DebugContextOptions> = buildDefaultOptions();\nlet lastIncident: Incident | null = null;\nlet globalHandlersAttached = false;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Initialises DebugContext with optional configuration.\n *\n * Call once at application startup — before your Express app starts\n * accepting requests.\n *\n * @example\n * ```ts\n * import DebugContext from '@debugcontext/core';\n *\n * DebugContext.init({ onIncident: (i) => console.log(i) });\n * ```\n */\nfunction init(userOptions: DebugContextOptions = {}): void {\n options = {\n ...buildDefaultOptions(),\n ...userOptions,\n sensitiveHeaders: [\n ...(userOptions.sensitiveHeaders ?? []),\n ],\n sensitiveFields: [\n ...(userOptions.sensitiveFields ?? []),\n ],\n };\n\n // Warm up the Git cache on init so the first incident is fast\n collectGit();\n\n if (options.captureGlobalErrors && !globalHandlersAttached) {\n attachGlobalHandlers();\n globalHandlersAttached = true;\n }\n}\n\n/**\n * Captures an error (and an optional request context provided by a framework\n * adapter) into a structured Incident.\n *\n * @param error - The thrown value.\n * @param request - Optional request context from a framework adapter.\n * @returns The captured Incident.\n *\n * @example\n * ```ts\n * try {\n * riskyOperation();\n * } catch (err) {\n * const incident = DebugContext.capture(err);\n * DebugContext.toConsole(incident);\n * }\n * ```\n */\nfunction capture(error: unknown, request: RequestContext | null = null): Incident {\n const incident: Incident = {\n incidentId: generateId(),\n timestamp: new Date().toISOString(),\n request,\n runtime: collectRuntime(),\n system: collectSystem(),\n git: collectGit(),\n error: collectError(error),\n };\n\n lastIncident = incident;\n\n // Fire the user-supplied hook (non-blocking)\n if (options.onIncident) {\n Promise.resolve(options.onIncident(incident)).catch((hookErr: unknown) => {\n console.error(\"[DebugContext] onIncident hook threw:\", hookErr);\n });\n }\n\n return incident;\n}\n\n/**\n * Returns the most recently captured Incident as a JSON string.\n *\n * @param incident - Incident to serialise. Defaults to the last captured one.\n * @returns Pretty-printed JSON string, or `null` if no incident has been captured.\n */\nfunction toJSON(incident?: Incident): string | null {\n const target = incident ?? lastIncident;\n if (!target) return null;\n return JSON.stringify(target, null, 2);\n}\n\n/**\n * Prints the incident to the console in a human-readable format.\n *\n * @param incident - Incident to print. Defaults to the last captured one.\n */\nfunction toConsole(incident?: Incident): void {\n const target = incident ?? lastIncident;\n if (!target) {\n console.warn(\"[DebugContext] No incident to display.\");\n return;\n }\n\n const hr = \"─\".repeat(60);\n\n console.error(`\n${hr}\n🐛 DebugContext Incident ${target.incidentId}\n${hr}\n\n Error : ${target.error.name}: ${target.error.message}\n Timestamp : ${target.timestamp}\n Environment: ${target.runtime.environment}\n Node : ${target.runtime.nodeVersion}\n PID : ${target.runtime.pid}\n Hostname : ${target.runtime.hostname}\n Uptime : ${target.runtime.uptimeSeconds.toFixed(1)}s\n Commit : ${target.git.commitHash} (${target.git.branch})\n Version : ${target.git.packageVersion}\n\n Heap : ${target.system.heapUsagePercent}% used\n RSS : ${formatBytes(target.system.memory.rss)}\n\n${target.request ? formatRequest(target.request) : \" No request context.\"}\n\n Stack:\n${indentStack(target.error.stack ?? \"(no stack)\")}\n${hr}\n`);\n}\n\n/**\n * Appends the incident to a newline-delimited JSON (NDJSON) file.\n * Creates the file and parent directories if they don't exist.\n *\n * @param incident - Incident to write. Defaults to the last captured one.\n * @param options - `{ path?: string }` — defaults to `\"incidents.ndjson\"`.\n *\n * @example\n * ```ts\n * DebugContext.init({\n * onIncident: (i) => DebugContext.toFile(i, { path: 'logs/incidents.ndjson' }),\n * });\n * ```\n */\nfunction toFile(incident?: Incident, options?: ToFileOptions): void {\n const target = incident ?? lastIncident;\n if (!target) {\n console.warn(\"[DebugContext] No incident to write.\");\n return;\n }\n toFileUtil(target, options);\n}\n\n/**\n * Returns a framework-agnostic capture function pre-bound to the current\n * options. Useful when building custom adapters.\n *\n * For Express, use `@debugcontext/express` instead.\n *\n * @example\n * ```ts\n * // In a custom adapter:\n * const mw = DebugContext.middleware();\n * app.use((err, req, res, next) => {\n * const requestCtx = buildRequestCtx(req);\n * mw(err, requestCtx);\n * next(err);\n * });\n * ```\n */\nfunction middleware(): (error: unknown, request?: RequestContext | null) => Incident {\n return (error: unknown, request: RequestContext | null = null): Incident => {\n return capture(error, request);\n };\n}\n\n/**\n * Returns the current SDK options (useful for adapters).\n * @internal\n */\nfunction getOptions(): Required<DebugContextOptions> {\n return options;\n}\n\n// ---------------------------------------------------------------------------\n// Global error handlers\n// ---------------------------------------------------------------------------\n\nfunction attachGlobalHandlers(): void {\n process.on(\"uncaughtException\", (err: Error) => {\n const incident = capture(err, null);\n toConsole(incident);\n // Re-throw so Node's default behaviour (exit) still fires\n process.exitCode = 1;\n });\n\n process.on(\"unhandledRejection\", (reason: unknown) => {\n capture(reason, null);\n });\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction formatRequest(req: RequestContext): string {\n return [\n ` Request:`,\n ` ${req.method} ${req.url}`,\n ` IP : ${req.ip}`,\n ` User-Agent: ${req.userAgent}`,\n ].join(\"\\n\");\n}\n\nfunction formatBytes(bytes: number): string {\n const mb = bytes / 1024 / 1024;\n return `${mb.toFixed(1)} MB`;\n}\n\nfunction indentStack(stack: string): string {\n return stack\n .split(\"\\n\")\n .map((l) => ` ${l}`)\n .join(\"\\n\");\n}\n\n// ---------------------------------------------------------------------------\n// Default options factory\n// ---------------------------------------------------------------------------\n\nfunction buildDefaultOptions(): Required<DebugContextOptions> {\n return {\n sensitiveHeaders: [],\n sensitiveFields: [],\n onIncident: () => undefined,\n captureGlobalErrors: true,\n };\n}\n\n// ---------------------------------------------------------------------------\n// Named export object (tree-shakable)\n// ---------------------------------------------------------------------------\n\n/**\n * The DebugContext SDK.\n *\n * @example\n * ```ts\n * import DebugContext from '@debugcontext/core';\n *\n * DebugContext.init();\n * ```\n */\nconst DebugContext = {\n init,\n capture,\n toJSON,\n toConsole,\n toFile,\n middleware,\n /** @internal used by framework adapters */\n getOptions,\n} as const;\n\nexport default DebugContext;\nexport { init, capture, toJSON, toConsole, toFile, middleware, getOptions };\n","/**\n * @module redact\n * Utilities for scrubbing sensitive data from headers and request bodies\n * before they are included in an Incident.\n */\n\n/** Header names that are always redacted (case-insensitive comparison). */\nexport const DEFAULT_SENSITIVE_HEADERS: readonly string[] = [\n \"authorization\",\n \"cookie\",\n \"set-cookie\",\n \"x-api-key\",\n \"x-auth-token\",\n \"x-access-token\",\n \"proxy-authorization\",\n \"www-authenticate\",\n];\n\n/** Body / object field names that are always redacted (case-insensitive). */\nexport const DEFAULT_SENSITIVE_FIELDS: readonly string[] = [\n \"password\",\n \"passwd\",\n \"secret\",\n \"token\",\n \"apikey\",\n \"api_key\",\n \"accesstoken\",\n \"access_token\",\n \"refreshtoken\",\n \"refresh_token\",\n \"creditcard\",\n \"credit_card\",\n \"cardnumber\",\n \"card_number\",\n \"cvv\",\n \"ssn\",\n \"privatekey\",\n \"private_key\",\n];\n\nconst REDACTED = \"[REDACTED]\";\n\n/**\n * Returns a copy of `headers` where sensitive values are replaced with\n * `\"[REDACTED]\"`.\n *\n * @param headers - Raw header map (values may be string or string[]).\n * @param extraFields - Caller-supplied additional header names to redact.\n */\nexport function redactHeaders(\n headers: Record<string, string | string[] | undefined>,\n extraFields: string[] = []\n): Record<string, string> {\n const sensitiveSet = new Set(\n [...DEFAULT_SENSITIVE_HEADERS, ...extraFields].map((h) => h.toLowerCase())\n );\n\n const result: Record<string, string> = {};\n\n for (const [key, value] of Object.entries(headers)) {\n if (value === undefined) continue;\n const normalised = key.toLowerCase();\n const raw = Array.isArray(value) ? value.join(\", \") : value;\n result[key] = sensitiveSet.has(normalised) ? REDACTED : raw;\n }\n\n return result;\n}\n\n/**\n * Deeply walks `body` (plain objects / arrays) and replaces the *values* of\n * sensitive keys with `\"[REDACTED]\"`. Non-plain-object values are returned\n * as-is (e.g. strings, numbers, Buffers).\n *\n * @param body - The request body to sanitise.\n * @param extraFields - Caller-supplied additional field names to redact.\n */\nexport function redactBody(\n body: unknown,\n extraFields: string[] = []\n): unknown {\n const sensitiveSet = new Set(\n [...DEFAULT_SENSITIVE_FIELDS, ...extraFields].map((f) => f.toLowerCase())\n );\n\n return walk(body, sensitiveSet, 0);\n}\n\nconst MAX_DEPTH = 10;\n\nfunction walk(value: unknown, sensitive: Set<string>, depth: number): unknown {\n if (depth > MAX_DEPTH) return \"[MAX_DEPTH_EXCEEDED]\";\n if (value === null || typeof value !== \"object\") return value;\n\n if (Array.isArray(value)) {\n return value.map((item) => walk(item, sensitive, depth + 1));\n }\n\n const result: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n result[key] = sensitive.has(key.toLowerCase())\n ? REDACTED\n : walk(val, sensitive, depth + 1);\n }\n return result;\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lahin31/debugcontext-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zero-configuration debugging context SDK for Node.js",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"debugging",
|
|
7
|
+
"error",
|
|
8
|
+
"context",
|
|
9
|
+
"incident",
|
|
10
|
+
"nodejs",
|
|
11
|
+
"typescript"
|
|
12
|
+
],
|
|
13
|
+
"author": "DebugContext Contributors",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/debugcontext/debugcontext",
|
|
18
|
+
"directory": "packages/core"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"main": "./dist/index.cjs",
|
|
22
|
+
"module": "./dist/index.js",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"default": "./dist/index.js"
|
|
29
|
+
},
|
|
30
|
+
"require": {
|
|
31
|
+
"types": "./dist/index.d.cts",
|
|
32
|
+
"default": "./dist/index.cjs"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"dist",
|
|
38
|
+
"README.md"
|
|
39
|
+
],
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsup",
|
|
42
|
+
"dev": "tsup --watch",
|
|
43
|
+
"test": "vitest run",
|
|
44
|
+
"test:watch": "vitest",
|
|
45
|
+
"test:coverage": "vitest run --coverage",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"lint": "eslint src --ext .ts",
|
|
48
|
+
"clean": "rm -rf dist coverage"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.0.0",
|
|
52
|
+
"@vitest/coverage-v8": "^1.0.0",
|
|
53
|
+
"tsup": "^8.0.0",
|
|
54
|
+
"typescript": "^5.4.0",
|
|
55
|
+
"vitest": "^1.0.0"
|
|
56
|
+
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=20.0.0"
|
|
59
|
+
},
|
|
60
|
+
"sideEffects": false
|
|
61
|
+
}
|