@steve02081504/virtual-console 0.0.8 → 0.1.1
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 +97 -115
- package/browser.mjs +120 -17
- package/main.mjs +21 -0
- package/node.mjs +115 -19
- package/package.json +3 -2
- package/util.mjs +78 -0
package/README.md
CHANGED
|
@@ -3,43 +3,27 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@steve02081504/virtual-console)
|
|
4
4
|
[](https://github.com/steve02081504/virtual-console/issues)
|
|
5
5
|
|
|
6
|
-
A powerful and flexible virtual console for Node.js that allows you to capture, manipulate, and
|
|
6
|
+
A powerful and flexible virtual console for **Node.js and the Browser** that allows you to capture, manipulate, redirect, and transform terminal output.
|
|
7
7
|
|
|
8
|
-
`VirtualConsole`
|
|
8
|
+
`VirtualConsole` acts as a smart proxy for the global `console`, providing:
|
|
9
9
|
|
|
10
|
-
- **
|
|
11
|
-
- **
|
|
12
|
-
- **
|
|
13
|
-
- **Debugging:** Isolate and inspect output from specific parts of your application, even in highly concurrent scenarios.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## How It Works: The Global Console Proxy
|
|
18
|
-
|
|
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.
|
|
20
|
-
|
|
21
|
-
1. **The Proxy:** The new `console` object is a proxy. When you call a method like `console.log()`, the proxy intercepts the call.
|
|
22
|
-
|
|
23
|
-
2. **`AsyncLocalStorage` for Isolation:** The proxy uses `AsyncLocalStorage` to look up the currently active `VirtualConsole` instance for the current async operation.
|
|
24
|
-
|
|
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.
|
|
28
|
-
|
|
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.
|
|
10
|
+
- **Async Context Isolation:** Safely capture output per request or task using `AsyncLocalStorage` (Node.js) or stack-based scoping (Browser).
|
|
11
|
+
- **HTML Output Generation:** Automatically converts ANSI colors and console formatting (including `%c`) into HTML strings for display in web UIs or reports.
|
|
12
|
+
- **Zero-Refactoring:** Works by proxying the global `console`, so you don't need to change your existing logging code.
|
|
32
13
|
|
|
33
14
|
## Features
|
|
34
15
|
|
|
35
|
-
- **
|
|
36
|
-
- **
|
|
37
|
-
- **
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- **
|
|
41
|
-
- **
|
|
42
|
-
- **
|
|
16
|
+
- **Universal Compatibility:** Works in both Node.js and Browser environments.
|
|
17
|
+
- **Output Recording:** Captures `stdout` and `stderr` to plain text (`outputs`) and **HTML** (`outputsHtml`).
|
|
18
|
+
- **ANSI & HTML Support:**
|
|
19
|
+
- Node.js: Preserves ANSI color codes.
|
|
20
|
+
- Browser/HTML: Converts ANSI codes and `%c` CSS styles to inline HTML styles.
|
|
21
|
+
- **Concurrency-Safe (Node.js):** Uses `AsyncLocalStorage` to guarantee that output from concurrent async operations is captured independently.
|
|
22
|
+
- **Real Console Passthrough:** Optionally prints to the actual console/terminal while capturing.
|
|
23
|
+
- **FreshLine (Updatable Lines):** Stateful method for creating overwritable lines (e.g., progress bars).
|
|
24
|
+
- *Node.js:* Uses ANSI escape codes to overwrite lines.
|
|
25
|
+
- *Browser:* Falls back gracefully to standard logging (simulated behavior).
|
|
26
|
+
- **Custom Error Handling:** Dedicated interception for `console.error(new Error(...))`.
|
|
43
27
|
|
|
44
28
|
## Installation
|
|
45
29
|
|
|
@@ -47,95 +31,82 @@ This architecture means you **don't need to refactor your code to pass console i
|
|
|
47
31
|
npm install @steve02081504/virtual-console
|
|
48
32
|
```
|
|
49
33
|
|
|
50
|
-
##
|
|
34
|
+
## Usage
|
|
51
35
|
|
|
52
|
-
|
|
36
|
+
### 1. Basic Testing (Capture Output)
|
|
37
|
+
|
|
38
|
+
Wrap your function call in `hookAsyncContext` and assert the captured output.
|
|
53
39
|
|
|
54
40
|
```javascript
|
|
55
41
|
import { VirtualConsole } from '@steve02081504/virtual-console';
|
|
56
42
|
import { strict as assert } from 'node:assert';
|
|
57
43
|
|
|
58
|
-
// 1. A function that logs to the console
|
|
59
44
|
function greet(name) {
|
|
60
|
-
|
|
61
|
-
|
|
45
|
+
console.log(`Hello, ${name}!`);
|
|
46
|
+
console.error(new Error('Something broke'));
|
|
62
47
|
}
|
|
63
48
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
const vc = new VirtualConsole();
|
|
49
|
+
async function test() {
|
|
50
|
+
const vc = new VirtualConsole();
|
|
67
51
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
await vc.hookAsyncContext(() => greet('World'));
|
|
52
|
+
// Run inside the hook. All console calls are routed to 'vc'.
|
|
53
|
+
await vc.hookAsyncContext(() => greet('World'));
|
|
71
54
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
assert.strictEqual(vc.outputs, expectedOutput);
|
|
75
|
-
console.log('Test passed!');
|
|
55
|
+
assert.ok(vc.outputs.includes('Hello, World!'));
|
|
56
|
+
assert.ok(vc.outputs.includes('Error: Something broke'));
|
|
76
57
|
}
|
|
77
58
|
|
|
78
|
-
|
|
59
|
+
test();
|
|
79
60
|
```
|
|
80
61
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
### Use Case: Concurrent Progress Bars with `freshLine`
|
|
62
|
+
### 2. Generating HTML Output for Web UIs
|
|
84
63
|
|
|
85
|
-
|
|
64
|
+
One of the most powerful features is `outputsHtml`, which converts console formatting to valid HTML string.
|
|
86
65
|
|
|
87
66
|
```javascript
|
|
88
67
|
import { VirtualConsole } from '@steve02081504/virtual-console';
|
|
89
68
|
|
|
90
|
-
const vc = new VirtualConsole(
|
|
69
|
+
const vc = new VirtualConsole();
|
|
91
70
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
71
|
+
await vc.hookAsyncContext(() => {
|
|
72
|
+
// ANSI Colors (Node.js style)
|
|
73
|
+
console.log('\x1b[31mRed Text\x1b[0m');
|
|
74
|
+
|
|
75
|
+
// CSS Styling (Browser style - %c)
|
|
76
|
+
console.log('%cBig Blue Text', 'color: blue; font-size: 20px');
|
|
77
|
+
|
|
78
|
+
// Objects
|
|
79
|
+
console.log({ foo: 'bar' });
|
|
80
|
+
});
|
|
101
81
|
|
|
102
|
-
|
|
82
|
+
// Get the captured output as HTML
|
|
83
|
+
const html = vc.outputsHtml;
|
|
103
84
|
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
vc.hookAsyncContext(() => updateProgress('Process-B', 75)),
|
|
109
|
-
]);
|
|
110
|
-
|
|
111
|
-
console.log('All tasks finished!');
|
|
85
|
+
// Result example:
|
|
86
|
+
// <span style="color:rgb(170,0,0)">Red Text</span>
|
|
87
|
+
// <span style="color: blue; font-size: 20px">Big Blue Text</span>
|
|
88
|
+
// ...
|
|
112
89
|
```
|
|
113
90
|
|
|
114
|
-
###
|
|
91
|
+
### 3. Concurrent Tasks (Node.js)
|
|
115
92
|
|
|
116
|
-
|
|
93
|
+
In Node.js, `VirtualConsole` uses `AsyncLocalStorage` to ensure logs from concurrent tasks don't mix.
|
|
117
94
|
|
|
118
95
|
```javascript
|
|
119
96
|
import { VirtualConsole } from '@steve02081504/virtual-console';
|
|
120
97
|
|
|
121
|
-
const
|
|
122
|
-
|
|
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
|
-
});
|
|
98
|
+
const vc = new VirtualConsole({ realConsoleOutput: true });
|
|
131
99
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
});
|
|
100
|
+
async function work(id, duration) {
|
|
101
|
+
console.log(`Starting task ${id}`); // Captured by the specific context
|
|
102
|
+
await new Promise(r => setTimeout(r, duration));
|
|
103
|
+
console.log(`Finished task ${id}`);
|
|
104
|
+
}
|
|
136
105
|
|
|
137
|
-
|
|
138
|
-
|
|
106
|
+
await Promise.all([
|
|
107
|
+
vc.hookAsyncContext(() => work('A', 100)), // Captured in context A
|
|
108
|
+
vc.hookAsyncContext(() => work('B', 50)), // Captured in context B
|
|
109
|
+
]);
|
|
139
110
|
```
|
|
140
111
|
|
|
141
112
|
## API Reference
|
|
@@ -145,46 +116,57 @@ console.log('Reported errors:', reportedErrors.map(e => e.message));
|
|
|
145
116
|
Creates a new `VirtualConsole` instance.
|
|
146
117
|
|
|
147
118
|
- `options` `<object>`
|
|
148
|
-
- `realConsoleOutput` `<boolean>`: If `true`, output is also sent to the base console. **Default:** `false`.
|
|
149
|
-
- `recordOutput` `<boolean>`: If `true`, output is captured in
|
|
150
|
-
- `base_console` `<Console>`: The console instance
|
|
151
|
-
- `error_handler` `<function(Error): void>`:
|
|
152
|
-
- `supportsAnsi` `<boolean>`:
|
|
119
|
+
- `realConsoleOutput` `<boolean>`: If `true`, output is also sent to the base (real) console. **Default:** `false`.
|
|
120
|
+
- `recordOutput` `<boolean>`: If `true`, output is captured in `outputs` and `outputsHtml`. **Default:** `true`.
|
|
121
|
+
- `base_console` `<Console>`: The console instance to pass through to.
|
|
122
|
+
- `error_handler` `<function(Error): void>`: specific handler for `console.error(err)`.
|
|
123
|
+
- `supportsAnsi` `<boolean>`: Force enable/disable ANSI support (affects `freshLine`).
|
|
153
124
|
|
|
154
125
|
### `virtualConsole.hookAsyncContext(fn?)`
|
|
155
126
|
|
|
156
|
-
Hooks the virtual console into
|
|
157
|
-
|
|
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`.
|
|
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).
|
|
127
|
+
Hooks the virtual console into the current execution context.
|
|
160
128
|
|
|
161
|
-
|
|
129
|
+
- **`hookAsyncContext(fn)`**: Runs `fn` and routes all `console.*` calls inside it to this instance. Returns a `Promise` with the result of `fn`.
|
|
130
|
+
- **`hookAsyncContext()`**: (Advanced) Manually sets this instance as the active console for the current context.
|
|
162
131
|
|
|
163
|
-
|
|
132
|
+
### Properties
|
|
164
133
|
|
|
165
|
-
|
|
134
|
+
- **`vc.outputs`** `<string>`: Captured raw text output (includes ANSI codes in Node.js).
|
|
135
|
+
- **`vc.outputsHtml`** `<string>`: Captured output converted to HTML strings. Handles ANSI codes and `%c` styling.
|
|
166
136
|
|
|
167
|
-
###
|
|
137
|
+
### Methods
|
|
168
138
|
|
|
169
|
-
|
|
139
|
+
- **`console.freshLine(id, ...args)`**:
|
|
140
|
+
Prints a line that can be overwritten by subsequent calls with the same `id`. Useful for progress bars.
|
|
141
|
+
- *Node.js:* Uses ANSI cursor movements.
|
|
142
|
+
- *Browser:* Simulates behavior (appends new lines).
|
|
143
|
+
- **`vc.clear()`**: Clears `outputs` and `outputsHtml`.
|
|
144
|
+
- **`vc.error(err)`**: Custom error handling if configured.
|
|
170
145
|
|
|
171
|
-
|
|
146
|
+
## Platform Differences
|
|
172
147
|
|
|
173
|
-
|
|
174
|
-
- `...args` `<any>`: The content to print, same as `console.log()`.
|
|
148
|
+
### Node.js
|
|
175
149
|
|
|
176
|
-
|
|
150
|
+
- Implementation relies on `node:async_hooks` (`AsyncLocalStorage`).
|
|
151
|
+
- Context isolation works perfectly even across `setTimeout`, `Promise`, and other async boundaries.
|
|
152
|
+
- `freshLine` supports real terminal cursor manipulation.
|
|
177
153
|
|
|
178
|
-
|
|
154
|
+
### Browser
|
|
179
155
|
|
|
180
|
-
|
|
156
|
+
- Implementation relies on a global variable stack strategy.
|
|
157
|
+
- **Scope Limitation:** `hookAsyncContext(fn)` works for the duration of the function execution. However, strict "async" context propagation (like passing context into a `setTimeout` callback) is mimicked but may not be as robust as Node.js's native hooks.
|
|
158
|
+
- `freshLine` cannot erase previous lines in the real browser console limitations, so it appends logs instead.
|
|
181
159
|
|
|
182
|
-
|
|
160
|
+
## Integration for Library Authors
|
|
183
161
|
|
|
184
|
-
|
|
162
|
+
If you are building a library that manages its own async contexts, you can synchronize with `VirtualConsole` using:
|
|
185
163
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
## Contributing
|
|
164
|
+
```javascript
|
|
165
|
+
import { setGlobalConsoleReflect } from '@steve02081504/virtual-console';
|
|
189
166
|
|
|
190
|
-
|
|
167
|
+
setGlobalConsoleReflect(
|
|
168
|
+
(defaultConsole) => { /* return active console */ },
|
|
169
|
+
(consoleInstance) => { /* set active console */ },
|
|
170
|
+
(consoleInstance, fn) => { /* run fn in context */ }
|
|
171
|
+
);
|
|
172
|
+
```
|
package/browser.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { FullProxy } from 'full-proxy'
|
|
2
2
|
|
|
3
|
+
import { argsToHtml } from './util.mjs'
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* 存储原始的浏览器 console 对象。
|
|
5
7
|
*/
|
|
@@ -11,24 +13,85 @@ const originalConsole = window.console
|
|
|
11
13
|
* @returns {string} 格式化后的单行字符串。
|
|
12
14
|
*/
|
|
13
15
|
function formatArgs(args) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
if (args.length === 0) return ''
|
|
17
|
+
const format = args[0]
|
|
18
|
+
if (format?.constructor !== String)
|
|
19
|
+
return args.map(arg => {
|
|
20
|
+
if (Object(arg) instanceof String) return arg
|
|
21
|
+
if (arg instanceof Error && arg.stack) return arg.stack
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(arg, null, '\t')
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return String(arg)
|
|
27
|
+
}
|
|
28
|
+
}).join(' ')
|
|
29
|
+
|
|
30
|
+
let output = ''
|
|
31
|
+
let argIndex = 1
|
|
32
|
+
let lastIndex = 0
|
|
33
|
+
const regex = /%[sdifoOc%]/g
|
|
34
|
+
let match
|
|
35
|
+
|
|
36
|
+
while ((match = regex.exec(format)) !== null) {
|
|
37
|
+
output += format.slice(lastIndex, match.index)
|
|
38
|
+
lastIndex = regex.lastIndex
|
|
39
|
+
|
|
40
|
+
if (match[0] === '%%') {
|
|
41
|
+
output += '%'
|
|
42
|
+
continue
|
|
19
43
|
}
|
|
20
|
-
|
|
21
|
-
|
|
44
|
+
|
|
45
|
+
if (argIndex >= args.length) {
|
|
46
|
+
output += match[0]
|
|
47
|
+
continue
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const arg = args[argIndex++]
|
|
51
|
+
switch (match[0]) {
|
|
52
|
+
case '%c':
|
|
53
|
+
break
|
|
54
|
+
case '%s':
|
|
55
|
+
output += String(arg)
|
|
56
|
+
break
|
|
57
|
+
case '%d':
|
|
58
|
+
case '%i':
|
|
59
|
+
output += String(parseInt(arg))
|
|
60
|
+
break
|
|
61
|
+
case '%f':
|
|
62
|
+
output += String(parseFloat(arg))
|
|
63
|
+
break
|
|
64
|
+
case '%o':
|
|
65
|
+
case '%O':
|
|
66
|
+
try { output += JSON.stringify(arg, null, '\t') }
|
|
67
|
+
catch { output += String(arg) }
|
|
68
|
+
break
|
|
22
69
|
}
|
|
23
|
-
}
|
|
70
|
+
}
|
|
71
|
+
output += format.slice(lastIndex)
|
|
72
|
+
|
|
73
|
+
while (argIndex < args.length) {
|
|
74
|
+
const arg = args[argIndex++]
|
|
75
|
+
if (output) output += ' '
|
|
76
|
+
if (arg instanceof Error && arg.stack) output += arg.stack
|
|
77
|
+
else if ((arg === null || arg instanceof Object) && !(arg instanceof Function))
|
|
78
|
+
try { output += JSON.stringify(arg, null, '\t') }
|
|
79
|
+
catch { output += String(arg) }
|
|
80
|
+
|
|
81
|
+
else output += String(arg)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return output
|
|
24
85
|
}
|
|
25
86
|
|
|
26
87
|
/**
|
|
27
|
-
*
|
|
88
|
+
* 创建一个虚拟控制台,用于捕获输出,同时可以选择性地将输出传递给真实的控制台。
|
|
28
89
|
*/
|
|
29
90
|
export class VirtualConsole {
|
|
30
91
|
/** @type {string} - 捕获的所有输出 */
|
|
31
92
|
outputs = ''
|
|
93
|
+
/** @type {string} - 捕获的所有输出 (HTML) */
|
|
94
|
+
outputsHtml = ''
|
|
32
95
|
|
|
33
96
|
/** @type {object} - 最终合并后的配置项 */
|
|
34
97
|
options
|
|
@@ -43,6 +106,7 @@ export class VirtualConsole {
|
|
|
43
106
|
* @param {object} [options={}] - 配置选项。
|
|
44
107
|
* @param {boolean} [options.realConsoleOutput=false] - 如果为 true,则在捕获输出的同时,也调用底层控制台进行实际输出。
|
|
45
108
|
* @param {boolean} [options.recordOutput=true] - 如果为 true,则捕获输出并保存在 outputs 属性中。
|
|
109
|
+
* @param {function(Error): void} [options.error_handler=null] - 一个专门处理单个 Error 对象的错误处理器。
|
|
46
110
|
* @param {Console} [options.base_console=window.console] - 用于 realConsoleOutput 的底层控制台实例。
|
|
47
111
|
*/
|
|
48
112
|
constructor(options = {}) {
|
|
@@ -52,25 +116,32 @@ export class VirtualConsole {
|
|
|
52
116
|
this.options = {
|
|
53
117
|
realConsoleOutput: false,
|
|
54
118
|
recordOutput: true,
|
|
119
|
+
error_handler: null,
|
|
55
120
|
...options,
|
|
56
121
|
}
|
|
57
122
|
|
|
58
123
|
const methods = ['log', 'info', 'warn', 'debug', 'error', 'table', 'dir', 'assert', 'count', 'countReset', 'time', 'timeLog', 'timeEnd', 'group', 'groupCollapsed', 'groupEnd']
|
|
59
124
|
for (const method of methods)
|
|
60
|
-
if (
|
|
125
|
+
if (this.#base_console[method] instanceof Function)
|
|
126
|
+
/**
|
|
127
|
+
* 重写控制台方法
|
|
128
|
+
* @param {...any} args - 控制台方法的参数。
|
|
129
|
+
* @returns {void}
|
|
130
|
+
*/
|
|
61
131
|
this[method] = (...args) => {
|
|
132
|
+
if (method == 'error' && this.options.error_handler && args.length === 1 && args[0] instanceof Error) return this.options.error_handler(args[0])
|
|
62
133
|
this.#loggedFreshLineId = null // 任何常规输出都会中断 freshLine 序列
|
|
63
134
|
|
|
64
|
-
if (this.options.recordOutput)
|
|
135
|
+
if (this.options.recordOutput) {
|
|
65
136
|
this.outputs += formatArgs(args) + '\n'
|
|
137
|
+
this.outputsHtml += argsToHtml(args) + '<br/>\n'
|
|
138
|
+
}
|
|
66
139
|
|
|
67
140
|
// 实际输出
|
|
68
141
|
if (this.options.realConsoleOutput)
|
|
69
142
|
this.#base_console[method](...args)
|
|
70
143
|
}
|
|
71
144
|
|
|
72
|
-
|
|
73
|
-
|
|
74
145
|
this.freshLine = this.freshLine.bind(this)
|
|
75
146
|
this.clear = this.clear.bind(this)
|
|
76
147
|
}
|
|
@@ -92,8 +163,9 @@ export class VirtualConsole {
|
|
|
92
163
|
/**
|
|
93
164
|
* 若提供fn,则在新的异步上下文中执行fn,并将fn上下文的控制台替换为此对象。
|
|
94
165
|
* 否则,将当前异步上下文中的控制台替换为此对象。
|
|
95
|
-
* @
|
|
96
|
-
* @
|
|
166
|
+
* @template T - fn 函数的返回类型。
|
|
167
|
+
* @param {(() => T | Promise<T>) | undefined} [fn] - 在新的异步上下文中执行的函数。
|
|
168
|
+
* @returns {Promise<T> | void} 若提供fn,则返回 fn 函数的 Promise 结果;否则返回void。
|
|
97
169
|
*/
|
|
98
170
|
hookAsyncContext(fn) {
|
|
99
171
|
if (fn) return consoleReflectRun(this, fn)
|
|
@@ -118,23 +190,31 @@ export class VirtualConsole {
|
|
|
118
190
|
}
|
|
119
191
|
|
|
120
192
|
/**
|
|
121
|
-
*
|
|
193
|
+
* 清空捕获的输出,并选择性地清空真实控制台。
|
|
194
|
+
* @returns {void}
|
|
122
195
|
*/
|
|
123
196
|
clear() {
|
|
124
197
|
this.#loggedFreshLineId = null
|
|
125
198
|
this.outputs = ''
|
|
199
|
+
this.outputsHtml = ''
|
|
126
200
|
if (this.options.realConsoleOutput)
|
|
127
201
|
this.#base_console.clear()
|
|
128
202
|
|
|
129
203
|
}
|
|
130
204
|
}
|
|
131
205
|
|
|
206
|
+
/**
|
|
207
|
+
* 默认的全局虚拟控制台实例。
|
|
208
|
+
*/
|
|
132
209
|
export const defaultConsole = new VirtualConsole({
|
|
133
210
|
base_console: originalConsole,
|
|
134
211
|
recordOutput: false,
|
|
135
212
|
realConsoleOutput: true,
|
|
136
213
|
})
|
|
137
214
|
|
|
215
|
+
/**
|
|
216
|
+
* 全局控制台的附加属性。
|
|
217
|
+
*/
|
|
138
218
|
export const globalConsoleAdditionalProperties = {}
|
|
139
219
|
|
|
140
220
|
// 模拟 AsyncLocalStorage 的上下文存储
|
|
@@ -149,7 +229,7 @@ let consoleReflectSet = (v) => {
|
|
|
149
229
|
}
|
|
150
230
|
|
|
151
231
|
/**
|
|
152
|
-
* @template T
|
|
232
|
+
* @template T - fn 函数的返回类型
|
|
153
233
|
* @type {(value: VirtualConsole, fn: () => T | Promise<T>) => Promise<T>}
|
|
154
234
|
*/
|
|
155
235
|
let consoleReflectRun = async (v, fn) => {
|
|
@@ -165,11 +245,27 @@ let consoleReflectRun = async (v, fn) => {
|
|
|
165
245
|
}
|
|
166
246
|
|
|
167
247
|
// 暴露设置和获取反射逻辑的函数,以完全匹配原始API
|
|
248
|
+
/**
|
|
249
|
+
* 设置全局控制台反射逻辑
|
|
250
|
+
* @template T - fn 函数的返回类型
|
|
251
|
+
* @param {(console: Console) => Console} Reflect 将 console 参数映射到新的 console 对象的函数。
|
|
252
|
+
* @param {(console: Console) => void} ReflectSet 设置当前 console 对象的函数。
|
|
253
|
+
* @param {(console: Console, fn: () => T) => Promise<T>} ReflectRun 在新的异步上下文中执行函数的函数。
|
|
254
|
+
* @returns {void}
|
|
255
|
+
*/
|
|
168
256
|
export function setGlobalConsoleReflect(Reflect, ReflectSet, ReflectRun) {
|
|
257
|
+
/**
|
|
258
|
+
* 从默认控制台获取当前控制台对象。
|
|
259
|
+
* @returns {Console} 当前控制台对象。
|
|
260
|
+
*/
|
|
169
261
|
consoleReflect = () => Reflect(defaultConsole)
|
|
170
262
|
consoleReflectSet = ReflectSet
|
|
171
263
|
consoleReflectRun = ReflectRun
|
|
172
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* 获取全局控制台反射逻辑。
|
|
267
|
+
* @returns {object} 包含 Reflect、ReflectSet 和 ReflectRun 函数的对象。
|
|
268
|
+
*/
|
|
173
269
|
export function getGlobalConsoleReflect() {
|
|
174
270
|
return {
|
|
175
271
|
Reflect: consoleReflect,
|
|
@@ -183,6 +279,13 @@ export function getGlobalConsoleReflect() {
|
|
|
183
279
|
* 这与原始 Node.js 版本的实现完全相同。
|
|
184
280
|
*/
|
|
185
281
|
export const console = globalThis.console = new FullProxy(() => Object.assign({}, globalConsoleAdditionalProperties, consoleReflect()), {
|
|
282
|
+
/**
|
|
283
|
+
* 设置属性时的处理逻辑。
|
|
284
|
+
* @param {object} target - 目标对象。
|
|
285
|
+
* @param {string | symbol} property - 要设置的属性名。
|
|
286
|
+
* @param {any} value - 要设置的属性值。
|
|
287
|
+
* @returns {boolean} 指示属性是否成功设置的布尔值。
|
|
288
|
+
*/
|
|
186
289
|
set: (target, property, value) => {
|
|
187
290
|
target = consoleReflect()
|
|
188
291
|
if (property in target) return Reflect.set(target, property, value)
|
package/main.mjs
CHANGED
|
@@ -1,9 +1,30 @@
|
|
|
1
1
|
const module = await import(globalThis.document ? './browser.mjs' : './node.mjs')
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @type {AsyncLocalStorage}
|
|
5
|
+
*/
|
|
3
6
|
export const consoleAsyncStorage = module.consoleAsyncStorage
|
|
7
|
+
/**
|
|
8
|
+
* @type {typeof VirtualConsole}
|
|
9
|
+
*/
|
|
4
10
|
export const VirtualConsole = module.VirtualConsole
|
|
11
|
+
/**
|
|
12
|
+
* @type {VirtualConsole}
|
|
13
|
+
*/
|
|
5
14
|
export const defaultConsole = module.defaultConsole
|
|
15
|
+
/**
|
|
16
|
+
* @type {object}
|
|
17
|
+
*/
|
|
6
18
|
export const globalConsoleAdditionalProperties = module.globalConsoleAdditionalProperties
|
|
19
|
+
/**
|
|
20
|
+
* @type {function}
|
|
21
|
+
*/
|
|
7
22
|
export const setGlobalConsoleReflect = module.setGlobalConsoleReflect
|
|
23
|
+
/**
|
|
24
|
+
* @type {function}
|
|
25
|
+
*/
|
|
8
26
|
export const getGlobalConsoleReflect = module.getGlobalConsoleReflect
|
|
27
|
+
/**
|
|
28
|
+
* @type {VirtualConsole}
|
|
29
|
+
*/
|
|
9
30
|
export const console = module.console
|
package/node.mjs
CHANGED
|
@@ -7,6 +7,11 @@ import ansiEscapes from 'ansi-escapes'
|
|
|
7
7
|
import { FullProxy } from 'full-proxy'
|
|
8
8
|
import supportsAnsi from 'supports-ansi'
|
|
9
9
|
|
|
10
|
+
import { argsToHtml } from './util.mjs'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 全局异步存储,用于管理控制台上下文。
|
|
14
|
+
*/
|
|
10
15
|
export const consoleAsyncStorage = new AsyncLocalStorage()
|
|
11
16
|
const cleanupRegistry = new FinalizationRegistry(cleanupToken => {
|
|
12
17
|
const { stream, listener } = cleanupToken
|
|
@@ -15,27 +20,28 @@ const cleanupRegistry = new FinalizationRegistry(cleanupToken => {
|
|
|
15
20
|
|
|
16
21
|
/**
|
|
17
22
|
* 创建一个虚拟控制台,用于捕获输出,同时可以选择性地将输出传递给真实的控制台。
|
|
18
|
-
*
|
|
19
|
-
* @extends {Console}
|
|
23
|
+
* @augments {Console}
|
|
20
24
|
*/
|
|
21
25
|
export class VirtualConsole extends Console {
|
|
22
26
|
/**
|
|
23
|
-
*
|
|
27
|
+
* 在新的异步上下文中执行fn,并将该上下文的控制台替换为此对象。
|
|
28
|
+
* 这是通过 Node.js 的 AsyncLocalStorage 实现的。
|
|
24
29
|
* @template T
|
|
25
30
|
* @overload
|
|
26
|
-
* @param {() => T} fn -
|
|
31
|
+
* @param {() => T | Promise<T>} fn - 在新的异步上下文中执行的函数。
|
|
27
32
|
* @returns {Promise<T>} 返回 fn 函数的 Promise 结果。
|
|
28
33
|
*/
|
|
29
34
|
/**
|
|
30
|
-
*
|
|
35
|
+
* 将当前“异步上下文”中的控制台替换为此对象。
|
|
31
36
|
* @overload
|
|
32
37
|
* @returns {void}
|
|
33
38
|
*/
|
|
34
39
|
/**
|
|
35
|
-
* 若提供fn
|
|
36
|
-
*
|
|
37
|
-
* @
|
|
38
|
-
* @
|
|
40
|
+
* 若提供fn,则在新的异步上下文中执行fn,并将fn上下文的控制台替换为此对象。
|
|
41
|
+
* 否则,将当前异步上下文中的控制台替换为此对象。
|
|
42
|
+
* @template T - fn 函数的返回类型。
|
|
43
|
+
* @param {(() => T | Promise<T>) | undefined} [fn] - 在新的异步上下文中执行的函数。
|
|
44
|
+
* @returns {Promise<T> | void} 若提供fn,则返回 fn 函数的 Promise 结果;否则返回void。
|
|
39
45
|
*/
|
|
40
46
|
hookAsyncContext(fn) {
|
|
41
47
|
if (fn) return consoleReflectRun(this, fn)
|
|
@@ -43,6 +49,8 @@ export class VirtualConsole extends Console {
|
|
|
43
49
|
}
|
|
44
50
|
/** @type {string} - 捕获的所有输出 */
|
|
45
51
|
outputs = ''
|
|
52
|
+
/** @type {string} - 捕获的所有输出 (HTML) */
|
|
53
|
+
outputsHtml = ''
|
|
46
54
|
|
|
47
55
|
/** @type {object} - 最终合并后的配置项 */
|
|
48
56
|
options
|
|
@@ -57,11 +65,12 @@ export class VirtualConsole extends Console {
|
|
|
57
65
|
* @param {object} [options={}] - 配置选项。
|
|
58
66
|
* @param {boolean} [options.realConsoleOutput=false] - 如果为 true,则在捕获输出的同时,也调用底层控制台进行实际输出。
|
|
59
67
|
* @param {boolean} [options.recordOutput=true] - 如果为 true,则捕获输出并保存在 outputs 属性中。
|
|
68
|
+
* @param {boolean} [options.supportsAnsi] - 如果为 true, 则启用 ANSI 转义序列支持。
|
|
60
69
|
* @param {function(Error): void} [options.error_handler=null] - 一个专门处理单个 Error 对象的错误处理器。
|
|
61
70
|
* @param {Console} [options.base_console=console] - 用于 realConsoleOutput 的底层控制台实例。
|
|
62
71
|
*/
|
|
63
72
|
constructor(options = {}) {
|
|
64
|
-
super(new Writable({ write: () => { } }), new Writable({ write: () => { } }))
|
|
73
|
+
super(new Writable({ /** 啥也不干 */ write: () => { } }), new Writable({ /** 啥也不干 */ write: () => { } }))
|
|
65
74
|
|
|
66
75
|
this.base_console = options.base_console || consoleReflect()
|
|
67
76
|
delete options.base_console
|
|
@@ -77,8 +86,14 @@ export class VirtualConsole extends Console {
|
|
|
77
86
|
for (const method of ['log', 'info', 'warn', 'debug', 'error']) {
|
|
78
87
|
if (!this[method]) continue
|
|
79
88
|
const originalMethod = this[method]
|
|
89
|
+
/**
|
|
90
|
+
* 将控制台方法重写为捕获输出并根据配置决定是否传递给底层控制台。
|
|
91
|
+
* @param {...any} args - 控制台方法的参数。
|
|
92
|
+
* @returns {void}
|
|
93
|
+
*/
|
|
80
94
|
this[method] = (...args) => {
|
|
81
95
|
if (method == 'error' && this.options.error_handler && args.length === 1 && args[0] instanceof Error) return this.options.error_handler(args[0])
|
|
96
|
+
if (this.options.recordOutput) this.outputsHtml += argsToHtml(args) + '<br/>\n'
|
|
82
97
|
if (!this.options.realConsoleOutput || this.options.recordOutput) return originalMethod.apply(this, args)
|
|
83
98
|
this.#loggedFreshLineId = null
|
|
84
99
|
return this.#base_console[method](...args)
|
|
@@ -86,15 +101,35 @@ export class VirtualConsole extends Console {
|
|
|
86
101
|
}
|
|
87
102
|
}
|
|
88
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 获取用于 realConsoleOutput 的底层控制台实例。
|
|
106
|
+
* @returns {Console} 底层控制台实例。
|
|
107
|
+
*/
|
|
89
108
|
get base_console() {
|
|
90
109
|
return this.#base_console
|
|
91
110
|
}
|
|
92
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 设置用于 realConsoleOutput 的底层控制台实例。
|
|
114
|
+
* @param {Console} value - 底层控制台实例。
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
93
117
|
set base_console(value) {
|
|
94
118
|
this.#base_console = value
|
|
95
119
|
|
|
120
|
+
/**
|
|
121
|
+
* 创建一个虚拟的控制台流,用于捕获输出。
|
|
122
|
+
* @param {NodeJS.WritableStream} targetStream - 目标流。
|
|
123
|
+
* @returns {NodeJS.WritableStream} 虚拟流。
|
|
124
|
+
*/
|
|
96
125
|
const createVirtualStream = (targetStream) => {
|
|
97
126
|
const virtualStream = new Writable({
|
|
127
|
+
/**
|
|
128
|
+
* 写入数据到虚拟流。
|
|
129
|
+
* @param {Buffer | string} chunk - 要写入的数据块。
|
|
130
|
+
* @param {string} encoding - 编码格式。
|
|
131
|
+
* @param {() => void} callback - 写入完成的回调函数。
|
|
132
|
+
*/
|
|
98
133
|
write: (chunk, encoding, callback) => {
|
|
99
134
|
this.#loggedFreshLineId = null
|
|
100
135
|
|
|
@@ -110,14 +145,42 @@ export class VirtualConsole extends Console {
|
|
|
110
145
|
if (targetStream.isTTY) {
|
|
111
146
|
Object.defineProperties(virtualStream, {
|
|
112
147
|
isTTY: { value: true, configurable: true, writable: false, enumerable: true },
|
|
113
|
-
columns: {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
columns: {
|
|
149
|
+
/**
|
|
150
|
+
* 获取目标流的列数
|
|
151
|
+
* @returns {number} 列数
|
|
152
|
+
*/
|
|
153
|
+
get: () => targetStream.columns, configurable: true, enumerable: true
|
|
154
|
+
},
|
|
155
|
+
rows: {
|
|
156
|
+
/**
|
|
157
|
+
* 获取目标流的行数
|
|
158
|
+
* @returns {number} 行数
|
|
159
|
+
*/
|
|
160
|
+
get: () => targetStream.rows, configurable: true, enumerable: true
|
|
161
|
+
},
|
|
162
|
+
getColorDepth: {
|
|
163
|
+
/**
|
|
164
|
+
* 获取目标流的颜色深度
|
|
165
|
+
* @returns {number} 颜色深度
|
|
166
|
+
*/
|
|
167
|
+
get: () => targetStream.getColorDepth.bind(targetStream), configurable: true, enumerable: true
|
|
168
|
+
},
|
|
169
|
+
hasColors: {
|
|
170
|
+
/**
|
|
171
|
+
* 判断目标流是否支持颜色
|
|
172
|
+
* @returns {boolean} 是否支持颜色
|
|
173
|
+
*/
|
|
174
|
+
get: () => targetStream.hasColors.bind(targetStream), configurable: true, enumerable: true
|
|
175
|
+
},
|
|
117
176
|
})
|
|
118
177
|
|
|
119
178
|
const virtualStreamRef = new WeakRef(virtualStream)
|
|
120
179
|
|
|
180
|
+
/**
|
|
181
|
+
* 监听目标流的 resize 事件,并在虚拟流上触发相应的事件。
|
|
182
|
+
* @returns {void}
|
|
183
|
+
*/
|
|
121
184
|
const resizeListener = () => {
|
|
122
185
|
virtualStreamRef.deref()?.emit('resize')
|
|
123
186
|
}
|
|
@@ -151,35 +214,58 @@ export class VirtualConsole extends Console {
|
|
|
151
214
|
this.#loggedFreshLineId = id
|
|
152
215
|
}
|
|
153
216
|
|
|
217
|
+
/**
|
|
218
|
+
* 清空捕获的输出,并选择性地清空真实控制台。
|
|
219
|
+
* @returns {void}
|
|
220
|
+
*/
|
|
154
221
|
clear() {
|
|
155
222
|
this.#loggedFreshLineId = null
|
|
156
223
|
this.outputs = ''
|
|
224
|
+
this.outputsHtml = ''
|
|
157
225
|
if (this.options.realConsoleOutput)
|
|
158
226
|
this.#base_console.clear()
|
|
159
227
|
}
|
|
160
228
|
}
|
|
161
229
|
|
|
162
230
|
const originalConsole = globalThis.console
|
|
231
|
+
/**
|
|
232
|
+
* 默认的虚拟控制台实例。
|
|
233
|
+
*/
|
|
163
234
|
export const defaultConsole = new VirtualConsole({ base_console: originalConsole, recordOutput: false, realConsoleOutput: true })
|
|
235
|
+
/**
|
|
236
|
+
* 全局控制台的附加属性。
|
|
237
|
+
*/
|
|
164
238
|
export const globalConsoleAdditionalProperties = {}
|
|
165
239
|
/** @type {() => VirtualConsole} */
|
|
166
240
|
let consoleReflect = () => consoleAsyncStorage.getStore() ?? defaultConsole
|
|
167
241
|
/** @type {(value: VirtualConsole) => void} */
|
|
168
242
|
let consoleReflectSet = (v) => consoleAsyncStorage.enterWith(v)
|
|
169
|
-
/**
|
|
243
|
+
/**
|
|
244
|
+
* @template T - fn 函数的返回类型
|
|
245
|
+
* @type {(value: VirtualConsole, fn: () => T) => Promise<T>}
|
|
246
|
+
*/
|
|
170
247
|
let consoleReflectRun = (v, fn) => consoleAsyncStorage.run(v, fn)
|
|
171
248
|
/**
|
|
172
249
|
* 设置全局控制台反射逻辑
|
|
173
|
-
* @template T
|
|
174
|
-
* @param {(console: Console) => Console} Reflect
|
|
175
|
-
* @param {(value: Console) => void} ReflectSet
|
|
176
|
-
* @param {(value: Console, fn: () => T) => Promise<T>} ReflectRun
|
|
250
|
+
* @template T - fn 函数的返回类型
|
|
251
|
+
* @param {(console: Console) => Console} Reflect 从默认控制台映射到新的控制台对象的函数。
|
|
252
|
+
* @param {(value: Console) => void} ReflectSet 设置当前控制台对象的函数。
|
|
253
|
+
* @param {(value: Console, fn: () => T) => Promise<T>} ReflectRun 在新的异步上下文中执行函数的函数。
|
|
254
|
+
* @returns {void}
|
|
177
255
|
*/
|
|
178
256
|
export function setGlobalConsoleReflect(Reflect, ReflectSet, ReflectRun) {
|
|
257
|
+
/**
|
|
258
|
+
* 从默认控制台获取当前控制台对象。
|
|
259
|
+
* @returns {Console} 当前控制台对象。
|
|
260
|
+
*/
|
|
179
261
|
consoleReflect = () => Reflect(defaultConsole)
|
|
180
262
|
consoleReflectSet = ReflectSet
|
|
181
263
|
consoleReflectRun = ReflectRun
|
|
182
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* 获取全局控制台反射逻辑
|
|
267
|
+
* @returns {object} 包含 Reflect、ReflectSet 和 ReflectRun 函数的对象。
|
|
268
|
+
*/
|
|
183
269
|
export function getGlobalConsoleReflect() {
|
|
184
270
|
return {
|
|
185
271
|
Reflect: consoleReflect,
|
|
@@ -187,7 +273,17 @@ export function getGlobalConsoleReflect() {
|
|
|
187
273
|
ReflectRun: consoleReflectRun
|
|
188
274
|
}
|
|
189
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* 全局控制台实例。
|
|
278
|
+
*/
|
|
190
279
|
export const console = globalThis.console = new FullProxy(() => Object.assign({}, globalConsoleAdditionalProperties, consoleReflect()), {
|
|
280
|
+
/**
|
|
281
|
+
* 设置属性时的处理逻辑。
|
|
282
|
+
* @param {object} target - 目标对象。
|
|
283
|
+
* @param {string | symbol} property - 要设置的属性名。
|
|
284
|
+
* @param {any} value - 要设置的属性值。
|
|
285
|
+
* @returns {any} 属性值。
|
|
286
|
+
*/
|
|
191
287
|
set: (target, property, value) => {
|
|
192
288
|
target = consoleReflect()
|
|
193
289
|
if (property in target) return Reflect.set(target, property, value)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steve02081504/virtual-console",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A virtual console for capturing and manipulating terminal output.",
|
|
5
5
|
"main": "main.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"ansi-escapes": "latest",
|
|
21
21
|
"full-proxy": "latest",
|
|
22
|
-
"supports-ansi": "latest"
|
|
22
|
+
"supports-ansi": "latest",
|
|
23
|
+
"ansi_up": "latest"
|
|
23
24
|
}
|
|
24
25
|
}
|
package/util.mjs
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { AnsiUp } from 'ansi_up'
|
|
2
|
+
|
|
3
|
+
const ansi_up = new AnsiUp()
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 将 console 参数格式化为 HTML 字符串。
|
|
7
|
+
* @param {any[]} args - console 方法接收的参数数组。
|
|
8
|
+
* @returns {string} 格式化后的 HTML 字符串。
|
|
9
|
+
*/
|
|
10
|
+
export function argsToHtml(args) {
|
|
11
|
+
if (args.length === 0) return ''
|
|
12
|
+
const format = args[0]
|
|
13
|
+
if (format?.constructor !== String)
|
|
14
|
+
return args.map(arg => {
|
|
15
|
+
if (arg instanceof Error && arg.stack) return ansi_up.ansi_to_html(arg.stack)
|
|
16
|
+
if ((arg === null || arg instanceof Object) && !(arg instanceof Function))
|
|
17
|
+
try { return ansi_up.ansi_to_html(JSON.stringify(arg, null, '\t')) }
|
|
18
|
+
catch { return String(arg) }
|
|
19
|
+
|
|
20
|
+
return ansi_up.ansi_to_html(String(arg))
|
|
21
|
+
}).join(' ')
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
let html = ansi_up.ansi_to_html(format)
|
|
25
|
+
let argIndex = 1
|
|
26
|
+
let hasStyle = false
|
|
27
|
+
|
|
28
|
+
const regex = /%[sdifoOc%]/g
|
|
29
|
+
html = html.replace(regex, (match) => {
|
|
30
|
+
if (match === '%%') return '%'
|
|
31
|
+
if (argIndex >= args.length) return match
|
|
32
|
+
|
|
33
|
+
const arg = args[argIndex++]
|
|
34
|
+
switch (match) {
|
|
35
|
+
case '%c': {
|
|
36
|
+
hasStyle = true
|
|
37
|
+
const style = String(arg)
|
|
38
|
+
return `</span><span style="${style}">`
|
|
39
|
+
}
|
|
40
|
+
case '%s':
|
|
41
|
+
return ansi_up.ansi_to_html(String(arg))
|
|
42
|
+
case '%d':
|
|
43
|
+
case '%i':
|
|
44
|
+
return String(parseInt(arg))
|
|
45
|
+
case '%f':
|
|
46
|
+
return String(parseFloat(arg))
|
|
47
|
+
case '%o':
|
|
48
|
+
case '%O':
|
|
49
|
+
try { return ansi_up.ansi_to_html(JSON.stringify(arg)) }
|
|
50
|
+
catch { return String(arg) }
|
|
51
|
+
}
|
|
52
|
+
return match
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
if (hasStyle) html = `<span>${html}</span>`
|
|
56
|
+
|
|
57
|
+
const replaceTable = {
|
|
58
|
+
'<span style="">': '<span>',
|
|
59
|
+
'<span></span>': '',
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Object.entries(replaceTable).forEach(([key, value]) => {
|
|
63
|
+
html = html.replaceAll(key, value)
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
while (argIndex < args.length) {
|
|
67
|
+
const arg = args[argIndex++]
|
|
68
|
+
html += ' '
|
|
69
|
+
if (arg instanceof Error && arg.stack) html += ansi_up.ansi_to_html(arg.stack)
|
|
70
|
+
else if ((arg === null || arg instanceof Object) && !(arg instanceof Function))
|
|
71
|
+
try { html += ansi_up.ansi_to_html(JSON.stringify(arg, null, '\t')) }
|
|
72
|
+
catch { html += String(arg) }
|
|
73
|
+
|
|
74
|
+
else html += ansi_up.ansi_to_html(String(arg))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return html
|
|
78
|
+
}
|