@steve02081504/virtual-console 0.0.3 → 0.0.4
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 +46 -36
- package/main.mjs +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Virtual Console
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@steve02081504/virtual-console)
|
|
4
|
-
[](https://github.com/steve02081504/virtual-console/blob/main/LICENSE)
|
|
5
4
|
[](https://github.com/steve02081504/virtual-console/issues)
|
|
6
5
|
|
|
7
6
|
A powerful and flexible virtual console for Node.js that allows you to capture, manipulate, and redirect terminal output. Built with modern asynchronous contexts (`AsyncLocalStorage`) for robust, concurrency-safe operations.
|
|
@@ -15,27 +14,32 @@ A powerful and flexible virtual console for Node.js that allows you to capture,
|
|
|
15
14
|
|
|
16
15
|
---
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## How It Works: The Global Console Proxy
|
|
19
18
|
|
|
20
|
-
**This library is designed for zero-refactoring integration.** Upon import, it replaces `globalThis.console` with a smart proxy
|
|
19
|
+
**This library is designed for zero-refactoring integration.** Upon import, it replaces `globalThis.console` with a smart proxy that is aware of the asynchronous execution context.
|
|
21
20
|
|
|
22
|
-
1. **
|
|
21
|
+
1. **The Proxy:** The new `console` object is a proxy. When you call a method like `console.log()`, the proxy intercepts the call.
|
|
23
22
|
|
|
24
|
-
2.
|
|
23
|
+
2. **`AsyncLocalStorage` for Isolation:** The proxy uses `AsyncLocalStorage` to look up the currently active `VirtualConsole` instance for the current async operation.
|
|
25
24
|
|
|
26
|
-
3. **Context-Aware Routing:**
|
|
25
|
+
3. **Context-Aware Routing:**
|
|
26
|
+
- If you have activated a `VirtualConsole` instance using `vc.hookAsyncContext()`, the proxy finds your instance and routes all `console` calls to it. This allows your instance to capture output, handle stateful methods like `freshLine`, or apply custom logic.
|
|
27
|
+
- If no instance is active for the current context, the proxy forwards the call to a default, passthrough console that simply prints to the original terminal, just like the real `console` would.
|
|
27
28
|
|
|
28
|
-
This architecture means you **don't need to pass console instances around**. Just keep using the global `console` as you always have, and wrap the code you want to monitor.
|
|
29
|
+
This architecture means you **don't need to refactor your code to pass console instances around**. Just keep using the global `console` as you always have, and wrap the code you want to monitor in a hook.
|
|
30
|
+
|
|
31
|
+
**Enhanced Compatibility:** The proxy is designed to be a good citizen. If other libraries or your own code assign custom properties to the global `console` object (e.g., `console.myLogger = ...`), these properties are preserved and remain accessible.
|
|
29
32
|
|
|
30
33
|
## Features
|
|
31
34
|
|
|
32
35
|
- **Zero-Configuration Capturing:** Capture output from any module without changing its source code.
|
|
33
36
|
- **Concurrency-Safe Isolation:** Uses `AsyncLocalStorage` to guarantee that output from concurrent operations is captured independently and correctly.
|
|
34
|
-
- **Output Recording:** Captures all `stdout` and `stderr` output to a string property for inspection.
|
|
37
|
+
- **Output Recording:** Captures all `stdout` and `stderr` output to a string property for easy inspection.
|
|
35
38
|
- **Real Console Passthrough:** Optionally, print to the actual console while also capturing.
|
|
36
|
-
- **TTY Emulation:** Behaves like a real TTY, inheriting properties like `columns`, `rows`, and color support.
|
|
37
|
-
- **Updatable Lines (`freshLine`)**: A stateful method for creating overwritable lines, perfect for progress indicators.
|
|
38
|
-
- **
|
|
39
|
+
- **Full TTY Emulation:** Behaves like a real TTY, inheriting properties like `columns`, `rows`, and color support from the base console. It also respects terminal resize events.
|
|
40
|
+
- **Updatable Lines (`freshLine`)**: A stateful method for creating overwritable lines, perfect for progress indicators. Requires a TTY with ANSI support.
|
|
41
|
+
- **Custom Error Handling:** Provide a dedicated handler for `Error` objects passed to `console.error`.
|
|
42
|
+
- **Extensible:** Can be integrated with other async-context-aware libraries via `setGlobalConsoleReflect`.
|
|
39
43
|
|
|
40
44
|
## Installation
|
|
41
45
|
|
|
@@ -107,29 +111,31 @@ await Promise.all([
|
|
|
107
111
|
console.log('All tasks finished!');
|
|
108
112
|
```
|
|
109
113
|
|
|
110
|
-
### Use Case:
|
|
114
|
+
### Use Case: Custom Error Handling
|
|
111
115
|
|
|
112
|
-
You can
|
|
116
|
+
You can provide a dedicated `error_handler` to process `Error` objects passed to `console.error`. This is useful for integrating with logging services or custom error-reporting frameworks.
|
|
113
117
|
|
|
114
118
|
```javascript
|
|
115
|
-
|
|
119
|
+
import { VirtualConsole } from '@steve02081504/virtual-console';
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
console.log('Middleware: Activating virtual console for this request.');
|
|
119
|
-
// Activate `vc` for the rest of this async flow
|
|
120
|
-
vc.hookAsyncContext();
|
|
121
|
-
|
|
122
|
-
// Now, `next()` and any subsequent code in this request's
|
|
123
|
-
// async chain will have its console output handled by `vc`.
|
|
124
|
-
await next();
|
|
125
|
-
}
|
|
121
|
+
const reportedErrors = [];
|
|
126
122
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
const vc = new VirtualConsole({
|
|
124
|
+
// This handler is called ONLY when a single Error object is passed to console.error
|
|
125
|
+
error_handler: (err) => {
|
|
126
|
+
console.log(`Reporting error: "${err.message}"`);
|
|
127
|
+
reportedErrors.push(err);
|
|
128
|
+
},
|
|
129
|
+
realConsoleOutput: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
await vc.hookAsyncContext(() => {
|
|
133
|
+
console.error(new Error('Something went wrong!')); // Handled by error_handler
|
|
134
|
+
console.error('A regular error message.'); // Not an Error object, logged normally
|
|
135
|
+
});
|
|
130
136
|
|
|
131
|
-
//
|
|
132
|
-
|
|
137
|
+
// Verify the custom handler was called
|
|
138
|
+
console.log('Reported errors:', reportedErrors.map(e => e.message));
|
|
133
139
|
```
|
|
134
140
|
|
|
135
141
|
## API Reference
|
|
@@ -141,15 +147,15 @@ Creates a new `VirtualConsole` instance.
|
|
|
141
147
|
- `options` `<object>`
|
|
142
148
|
- `realConsoleOutput` `<boolean>`: If `true`, output is also sent to the base console. **Default:** `false`.
|
|
143
149
|
- `recordOutput` `<boolean>`: If `true`, output is captured in the `outputs` property. **Default:** `true`.
|
|
144
|
-
- `base_console` `<Console>`: The console instance for passthrough. **Default:** The original `global.console
|
|
145
|
-
- `error_handler` `<function(Error): void>`: A dedicated handler for `Error` objects passed to `console.error`.
|
|
146
|
-
- `supportsAnsi` `<boolean>`: Manually set ANSI support
|
|
150
|
+
- `base_console` `<Console>`: The console instance for passthrough. **Default:** The original `global.console` before it was patched.
|
|
151
|
+
- `error_handler` `<function(Error): void>`: A dedicated handler for `Error` objects passed to `console.error`. If `console.error` is called with a single argument that is an `instanceof Error`, this handler is invoked instead of the standard `console.error` logic. **Default:** `null`.
|
|
152
|
+
- `supportsAnsi` `<boolean>`: Manually set ANSI support, which affects `freshLine`. **Default:** Inherited from `base_console`'s stream, or the result of the `supports-ansi` package.
|
|
147
153
|
|
|
148
154
|
### `virtualConsole.hookAsyncContext(fn?)`
|
|
149
155
|
|
|
150
156
|
Hooks the virtual console into an asynchronous context.
|
|
151
157
|
|
|
152
|
-
- **`hookAsyncContext(fn)`**: Runs `fn` in a new context. All `console` calls within `fn` (and any functions it `await`s) are routed to this instance. Returns the
|
|
158
|
+
- **`hookAsyncContext(fn)`**: Runs `fn` in a new context. All `console` calls within `fn` (and any functions it `await`s) are routed to this instance. Returns a `Promise` that resolves with the return value of `fn`.
|
|
153
159
|
- **`hookAsyncContext()`**: Activates the instance for the *current* asynchronous context. Useful for "activating" a console and leaving it active for the remainder of an async flow (e.g., within middleware).
|
|
154
160
|
|
|
155
161
|
### `virtualConsole.outputs`
|
|
@@ -162,7 +168,7 @@ A string containing all captured `stdout` and `stderr` output.
|
|
|
162
168
|
|
|
163
169
|
*Note: This stateful method is available on the global `console` object but only works as intended inside a `hookAsyncContext`.*
|
|
164
170
|
|
|
165
|
-
Prints a line. If the previously printed line (within the same hook) had the same `id`, it overwrites the previous line.
|
|
171
|
+
Prints a line. If the previously printed line (within the same hook) had the same `id`, it overwrites the previous line using ANSI escape codes. This requires a TTY environment.
|
|
166
172
|
|
|
167
173
|
- `id` `<string>`: A unique identifier for the overwritable line.
|
|
168
174
|
- `...args` `<any>`: The content to print, same as `console.log()`.
|
|
@@ -171,10 +177,14 @@ Prints a line. If the previously printed line (within the same hook) had the sam
|
|
|
171
177
|
|
|
172
178
|
Clears the captured `outputs` string. If `realConsoleOutput` is enabled, it also attempts to call `clear()` on the base console.
|
|
173
179
|
|
|
174
|
-
###
|
|
180
|
+
### `virtualConsole.error(...args)`
|
|
181
|
+
|
|
182
|
+
The standard `console.error` method. However, if an `error_handler` is configured in the constructor, and `error()` is called with a single argument that is an `instanceof Error`, the handler will be invoked with the error object.
|
|
183
|
+
|
|
184
|
+
## For Library Authors: `setGlobalConsoleReflect(...)`
|
|
175
185
|
|
|
176
|
-
`VirtualConsole` exposes its `AsyncLocalStorage`-based reflection mechanism. If you are building another library that manages async context, you can use `setGlobalConsoleReflect` to integrate `VirtualConsole`'s logic into your own context manager
|
|
186
|
+
`VirtualConsole` exposes its `AsyncLocalStorage`-based reflection mechanism. If you are building another library that also manages async context (e.g., a custom APM or transaction tracer), you can use `setGlobalConsoleReflect` to integrate `VirtualConsole`'s logic into your own context manager. This prevents the two libraries from fighting over control of the async context. See the source code for details on its function signature.
|
|
177
187
|
|
|
178
188
|
## Contributing
|
|
179
189
|
|
|
180
|
-
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/steve02081504/
|
|
190
|
+
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/steve02081504/virtual-console).
|
package/main.mjs
CHANGED
|
@@ -156,6 +156,7 @@ export class VirtualConsole extends Console {
|
|
|
156
156
|
|
|
157
157
|
const originalConsole = globalThis.console
|
|
158
158
|
export const defaultConsole = new VirtualConsole({ base_console: originalConsole, recordOutput: false, realConsoleOutput: true })
|
|
159
|
+
export const globalConsoleAdditionalProperties = {}
|
|
159
160
|
/** @type {() => VirtualConsole} */
|
|
160
161
|
let consoleReflect = () => consoleAsyncStorage.getStore() ?? defaultConsole
|
|
161
162
|
/** @type {(value: VirtualConsole) => void} */
|
|
@@ -181,4 +182,11 @@ export function getGlobalConsoleReflect() {
|
|
|
181
182
|
ReflectRun: consoleReflectRun
|
|
182
183
|
}
|
|
183
184
|
}
|
|
184
|
-
export const console = globalThis.console = new FullProxy(() => consoleReflect())
|
|
185
|
+
export const console = globalThis.console = new FullProxy(() => Object.assign({}, globalConsoleAdditionalProperties, consoleReflect()), {
|
|
186
|
+
set: (target, property, value) => {
|
|
187
|
+
target = consoleReflect()
|
|
188
|
+
if (property in target) return Reflect.set(target, property, value)
|
|
189
|
+
globalConsoleAdditionalProperties[property] = value
|
|
190
|
+
return true
|
|
191
|
+
}
|
|
192
|
+
})
|