@ricsam/isolate-console 0.0.1 → 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/CHANGELOG.md +9 -0
- package/package.json +26 -7
- package/src/index.ts +215 -0
- package/src/setup.test.ts +509 -0
- package/tsconfig.json +8 -0
- package/README.md +0 -45
package/CHANGELOG.md
ADDED
package/package.json
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ricsam/isolate-console",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./src/index.ts",
|
|
10
|
+
"types": "./src/index.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "node --test --experimental-strip-types 'src/**/*.test.ts'",
|
|
16
|
+
"typecheck": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@ricsam/isolate-core": "*",
|
|
20
|
+
"isolated-vm": "^6"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^24",
|
|
24
|
+
"typescript": "^5"
|
|
25
|
+
},
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"isolated-vm": "^6"
|
|
28
|
+
}
|
|
10
29
|
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import ivm from "isolated-vm";
|
|
2
|
+
|
|
3
|
+
export interface ConsoleOptions {
|
|
4
|
+
onLog?: (level: string, ...args: unknown[]) => void;
|
|
5
|
+
onTime?: (label: string, duration: number) => void;
|
|
6
|
+
onTimeLog?: (label: string, duration: number, ...args: unknown[]) => void;
|
|
7
|
+
onCount?: (label: string, count: number) => void;
|
|
8
|
+
onCountReset?: (label: string) => void;
|
|
9
|
+
onGroup?: (label: string, collapsed: boolean) => void;
|
|
10
|
+
onGroupEnd?: () => void;
|
|
11
|
+
onClear?: () => void;
|
|
12
|
+
onAssert?: (condition: boolean, ...args: unknown[]) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface ConsoleHandle {
|
|
16
|
+
dispose(): void;
|
|
17
|
+
reset(): void;
|
|
18
|
+
getTimers(): Map<string, number>;
|
|
19
|
+
getCounters(): Map<string, number>;
|
|
20
|
+
getGroupDepth(): number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Setup console API in an isolated-vm context
|
|
25
|
+
*
|
|
26
|
+
* Injects console.log, console.warn, console.error, console.info, console.debug,
|
|
27
|
+
* console.trace, console.dir, console.table, console.time, console.timeEnd,
|
|
28
|
+
* console.timeLog, console.count, console.countReset, console.group,
|
|
29
|
+
* console.groupCollapsed, console.groupEnd, console.clear, console.assert
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const handle = await setupConsole(context, {
|
|
33
|
+
* onLog: (level, ...args) => console.log(`[${level}]`, ...args)
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
export async function setupConsole(
|
|
37
|
+
context: ivm.Context,
|
|
38
|
+
options?: ConsoleOptions
|
|
39
|
+
): Promise<ConsoleHandle> {
|
|
40
|
+
const opts = options ?? {};
|
|
41
|
+
|
|
42
|
+
// State management
|
|
43
|
+
const timers = new Map<string, number>();
|
|
44
|
+
const counters = new Map<string, number>();
|
|
45
|
+
let groupDepth = 0;
|
|
46
|
+
|
|
47
|
+
const global = context.global;
|
|
48
|
+
|
|
49
|
+
// Log-level methods
|
|
50
|
+
const logLevels = [
|
|
51
|
+
"log",
|
|
52
|
+
"warn",
|
|
53
|
+
"error",
|
|
54
|
+
"debug",
|
|
55
|
+
"info",
|
|
56
|
+
"trace",
|
|
57
|
+
"dir",
|
|
58
|
+
"table",
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
for (const level of logLevels) {
|
|
62
|
+
global.setSync(
|
|
63
|
+
`__console_${level}`,
|
|
64
|
+
new ivm.Callback((...args: unknown[]) => {
|
|
65
|
+
opts.onLog?.(level, ...args);
|
|
66
|
+
})
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Timing methods
|
|
71
|
+
global.setSync(
|
|
72
|
+
"__console_time",
|
|
73
|
+
new ivm.Callback((label?: string) => {
|
|
74
|
+
const l = label ?? "default";
|
|
75
|
+
timers.set(l, performance.now());
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
global.setSync(
|
|
80
|
+
"__console_timeEnd",
|
|
81
|
+
new ivm.Callback((label?: string) => {
|
|
82
|
+
const l = label ?? "default";
|
|
83
|
+
const start = timers.get(l);
|
|
84
|
+
if (start !== undefined) {
|
|
85
|
+
const duration = performance.now() - start;
|
|
86
|
+
timers.delete(l);
|
|
87
|
+
opts.onTime?.(l, duration);
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
global.setSync(
|
|
93
|
+
"__console_timeLog",
|
|
94
|
+
new ivm.Callback((label?: string, ...args: unknown[]) => {
|
|
95
|
+
const l = label ?? "default";
|
|
96
|
+
const start = timers.get(l);
|
|
97
|
+
if (start !== undefined) {
|
|
98
|
+
const duration = performance.now() - start;
|
|
99
|
+
opts.onTimeLog?.(l, duration, ...args);
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
// Counting methods
|
|
105
|
+
global.setSync(
|
|
106
|
+
"__console_count",
|
|
107
|
+
new ivm.Callback((label?: string) => {
|
|
108
|
+
const l = label ?? "default";
|
|
109
|
+
const count = (counters.get(l) ?? 0) + 1;
|
|
110
|
+
counters.set(l, count);
|
|
111
|
+
opts.onCount?.(l, count);
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
global.setSync(
|
|
116
|
+
"__console_countReset",
|
|
117
|
+
new ivm.Callback((label?: string) => {
|
|
118
|
+
const l = label ?? "default";
|
|
119
|
+
counters.delete(l);
|
|
120
|
+
opts.onCountReset?.(l);
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// Grouping methods
|
|
125
|
+
global.setSync(
|
|
126
|
+
"__console_group",
|
|
127
|
+
new ivm.Callback((label?: string) => {
|
|
128
|
+
const l = label ?? "default";
|
|
129
|
+
groupDepth++;
|
|
130
|
+
opts.onGroup?.(l, false);
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
global.setSync(
|
|
135
|
+
"__console_groupCollapsed",
|
|
136
|
+
new ivm.Callback((label?: string) => {
|
|
137
|
+
const l = label ?? "default";
|
|
138
|
+
groupDepth++;
|
|
139
|
+
opts.onGroup?.(l, true);
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
global.setSync(
|
|
144
|
+
"__console_groupEnd",
|
|
145
|
+
new ivm.Callback(() => {
|
|
146
|
+
if (groupDepth > 0) {
|
|
147
|
+
groupDepth--;
|
|
148
|
+
}
|
|
149
|
+
opts.onGroupEnd?.();
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Other methods
|
|
154
|
+
global.setSync(
|
|
155
|
+
"__console_clear",
|
|
156
|
+
new ivm.Callback(() => {
|
|
157
|
+
opts.onClear?.();
|
|
158
|
+
})
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
global.setSync(
|
|
162
|
+
"__console_assert",
|
|
163
|
+
new ivm.Callback((condition: boolean, ...args: unknown[]) => {
|
|
164
|
+
if (!condition) {
|
|
165
|
+
opts.onAssert?.(condition, ...args);
|
|
166
|
+
}
|
|
167
|
+
})
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Inject console object
|
|
171
|
+
context.evalSync(`
|
|
172
|
+
globalThis.console = {
|
|
173
|
+
log: __console_log,
|
|
174
|
+
warn: __console_warn,
|
|
175
|
+
error: __console_error,
|
|
176
|
+
debug: __console_debug,
|
|
177
|
+
info: __console_info,
|
|
178
|
+
trace: __console_trace,
|
|
179
|
+
dir: __console_dir,
|
|
180
|
+
table: __console_table,
|
|
181
|
+
time: __console_time,
|
|
182
|
+
timeEnd: __console_timeEnd,
|
|
183
|
+
timeLog: __console_timeLog,
|
|
184
|
+
count: __console_count,
|
|
185
|
+
countReset: __console_countReset,
|
|
186
|
+
group: __console_group,
|
|
187
|
+
groupCollapsed: __console_groupCollapsed,
|
|
188
|
+
groupEnd: __console_groupEnd,
|
|
189
|
+
clear: __console_clear,
|
|
190
|
+
assert: __console_assert,
|
|
191
|
+
};
|
|
192
|
+
`);
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
dispose() {
|
|
196
|
+
timers.clear();
|
|
197
|
+
counters.clear();
|
|
198
|
+
groupDepth = 0;
|
|
199
|
+
},
|
|
200
|
+
reset() {
|
|
201
|
+
timers.clear();
|
|
202
|
+
counters.clear();
|
|
203
|
+
groupDepth = 0;
|
|
204
|
+
},
|
|
205
|
+
getTimers() {
|
|
206
|
+
return new Map(timers);
|
|
207
|
+
},
|
|
208
|
+
getCounters() {
|
|
209
|
+
return new Map(counters);
|
|
210
|
+
},
|
|
211
|
+
getGroupDepth() {
|
|
212
|
+
return groupDepth;
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
import { test, describe, beforeEach, afterEach } from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import ivm from "isolated-vm";
|
|
4
|
+
import { setupConsole, type ConsoleHandle } from "./index.ts";
|
|
5
|
+
|
|
6
|
+
describe("@ricsam/isolate-console", () => {
|
|
7
|
+
let isolate: ivm.Isolate;
|
|
8
|
+
let context: ivm.Context;
|
|
9
|
+
|
|
10
|
+
beforeEach(async () => {
|
|
11
|
+
isolate = new ivm.Isolate();
|
|
12
|
+
context = await isolate.createContext();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
context.release();
|
|
17
|
+
isolate.dispose();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("log-level methods", () => {
|
|
21
|
+
test("console.log calls onLog with correct level", async () => {
|
|
22
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
23
|
+
await setupConsole(context, {
|
|
24
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
25
|
+
});
|
|
26
|
+
context.evalSync(`console.log("hello", 123)`);
|
|
27
|
+
assert.strictEqual(logCalls.length, 1);
|
|
28
|
+
assert.strictEqual(logCalls[0].level, "log");
|
|
29
|
+
assert.deepStrictEqual(logCalls[0].args, ["hello", 123]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("console.warn calls onLog with warn level", async () => {
|
|
33
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
34
|
+
await setupConsole(context, {
|
|
35
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
36
|
+
});
|
|
37
|
+
context.evalSync(`console.warn("warning")`);
|
|
38
|
+
assert.strictEqual(logCalls.length, 1);
|
|
39
|
+
assert.strictEqual(logCalls[0].level, "warn");
|
|
40
|
+
assert.deepStrictEqual(logCalls[0].args, ["warning"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("console.error calls onLog with error level", async () => {
|
|
44
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
45
|
+
await setupConsole(context, {
|
|
46
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
47
|
+
});
|
|
48
|
+
context.evalSync(`console.error("error message")`);
|
|
49
|
+
assert.strictEqual(logCalls.length, 1);
|
|
50
|
+
assert.strictEqual(logCalls[0].level, "error");
|
|
51
|
+
assert.deepStrictEqual(logCalls[0].args, ["error message"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("console.debug calls onLog with debug level", async () => {
|
|
55
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
56
|
+
await setupConsole(context, {
|
|
57
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
58
|
+
});
|
|
59
|
+
context.evalSync(`console.debug("debug info")`);
|
|
60
|
+
assert.strictEqual(logCalls.length, 1);
|
|
61
|
+
assert.strictEqual(logCalls[0].level, "debug");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("console.info calls onLog with info level", async () => {
|
|
65
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
66
|
+
await setupConsole(context, {
|
|
67
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
68
|
+
});
|
|
69
|
+
context.evalSync(`console.info("information")`);
|
|
70
|
+
assert.strictEqual(logCalls.length, 1);
|
|
71
|
+
assert.strictEqual(logCalls[0].level, "info");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("console.trace calls onLog with trace level", async () => {
|
|
75
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
76
|
+
await setupConsole(context, {
|
|
77
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
78
|
+
});
|
|
79
|
+
context.evalSync(`console.trace("trace")`);
|
|
80
|
+
assert.strictEqual(logCalls.length, 1);
|
|
81
|
+
assert.strictEqual(logCalls[0].level, "trace");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("console.dir calls onLog with dir level", async () => {
|
|
85
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
86
|
+
await setupConsole(context, {
|
|
87
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
88
|
+
});
|
|
89
|
+
context.evalSync(`console.dir({ key: "value" })`);
|
|
90
|
+
assert.strictEqual(logCalls.length, 1);
|
|
91
|
+
assert.strictEqual(logCalls[0].level, "dir");
|
|
92
|
+
assert.deepStrictEqual(logCalls[0].args, [{ key: "value" }]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("console.table calls onLog with table level", async () => {
|
|
96
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
97
|
+
await setupConsole(context, {
|
|
98
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
99
|
+
});
|
|
100
|
+
context.evalSync(`console.table([1, 2, 3])`);
|
|
101
|
+
assert.strictEqual(logCalls.length, 1);
|
|
102
|
+
assert.strictEqual(logCalls[0].level, "table");
|
|
103
|
+
assert.deepStrictEqual(logCalls[0].args, [[1, 2, 3]]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("console.log with no arguments", async () => {
|
|
107
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
108
|
+
await setupConsole(context, {
|
|
109
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
110
|
+
});
|
|
111
|
+
context.evalSync(`console.log()`);
|
|
112
|
+
assert.strictEqual(logCalls.length, 1);
|
|
113
|
+
assert.deepStrictEqual(logCalls[0].args, []);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("console.log with multiple arguments", async () => {
|
|
117
|
+
const logCalls: Array<{ level: string; args: unknown[] }> = [];
|
|
118
|
+
await setupConsole(context, {
|
|
119
|
+
onLog: (level, ...args) => logCalls.push({ level, args }),
|
|
120
|
+
});
|
|
121
|
+
context.evalSync(`console.log("a", "b", "c", 1, 2, 3)`);
|
|
122
|
+
assert.strictEqual(logCalls.length, 1);
|
|
123
|
+
assert.deepStrictEqual(logCalls[0].args, ["a", "b", "c", 1, 2, 3]);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("timing methods", () => {
|
|
128
|
+
test("console.time starts a timer", async () => {
|
|
129
|
+
const handle = await setupConsole(context);
|
|
130
|
+
context.evalSync(`console.time("test")`);
|
|
131
|
+
assert.strictEqual(handle.getTimers().has("test"), true);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("console.time uses default label", async () => {
|
|
135
|
+
const handle = await setupConsole(context);
|
|
136
|
+
context.evalSync(`console.time()`);
|
|
137
|
+
assert.strictEqual(handle.getTimers().has("default"), true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("console.timeEnd reports duration", async () => {
|
|
141
|
+
const timeCalls: Array<{ label: string; duration: number }> = [];
|
|
142
|
+
const handle = await setupConsole(context, {
|
|
143
|
+
onTime: (label, duration) => timeCalls.push({ label, duration }),
|
|
144
|
+
});
|
|
145
|
+
context.evalSync(`console.time("test")`);
|
|
146
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
147
|
+
context.evalSync(`console.timeEnd("test")`);
|
|
148
|
+
|
|
149
|
+
assert.strictEqual(timeCalls.length, 1);
|
|
150
|
+
assert.strictEqual(timeCalls[0].label, "test");
|
|
151
|
+
assert.ok(timeCalls[0].duration >= 0);
|
|
152
|
+
assert.strictEqual(handle.getTimers().has("test"), false);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("console.timeEnd with default label", async () => {
|
|
156
|
+
const timeCalls: Array<{ label: string; duration: number }> = [];
|
|
157
|
+
await setupConsole(context, {
|
|
158
|
+
onTime: (label, duration) => timeCalls.push({ label, duration }),
|
|
159
|
+
});
|
|
160
|
+
context.evalSync(`console.time()`);
|
|
161
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
162
|
+
context.evalSync(`console.timeEnd()`);
|
|
163
|
+
|
|
164
|
+
assert.strictEqual(timeCalls.length, 1);
|
|
165
|
+
assert.strictEqual(timeCalls[0].label, "default");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("console.timeEnd with non-existent timer is no-op", async () => {
|
|
169
|
+
const timeCalls: Array<{ label: string; duration: number }> = [];
|
|
170
|
+
await setupConsole(context, {
|
|
171
|
+
onTime: (label, duration) => timeCalls.push({ label, duration }),
|
|
172
|
+
});
|
|
173
|
+
context.evalSync(`console.timeEnd("nonexistent")`);
|
|
174
|
+
assert.strictEqual(timeCalls.length, 0);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("console.timeLog reports duration without ending", async () => {
|
|
178
|
+
const timeLogCalls: Array<{
|
|
179
|
+
label: string;
|
|
180
|
+
duration: number;
|
|
181
|
+
args: unknown[];
|
|
182
|
+
}> = [];
|
|
183
|
+
const handle = await setupConsole(context, {
|
|
184
|
+
onTimeLog: (label, duration, ...args) =>
|
|
185
|
+
timeLogCalls.push({ label, duration, args }),
|
|
186
|
+
});
|
|
187
|
+
context.evalSync(`console.time("test")`);
|
|
188
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
189
|
+
context.evalSync(`console.timeLog("test", "additional", "args")`);
|
|
190
|
+
|
|
191
|
+
assert.strictEqual(timeLogCalls.length, 1);
|
|
192
|
+
assert.strictEqual(timeLogCalls[0].label, "test");
|
|
193
|
+
assert.deepStrictEqual(timeLogCalls[0].args, ["additional", "args"]);
|
|
194
|
+
assert.strictEqual(handle.getTimers().has("test"), true); // Timer still running
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("console.timeLog with non-existent timer is no-op", async () => {
|
|
198
|
+
const timeLogCalls: Array<{
|
|
199
|
+
label: string;
|
|
200
|
+
duration: number;
|
|
201
|
+
args: unknown[];
|
|
202
|
+
}> = [];
|
|
203
|
+
await setupConsole(context, {
|
|
204
|
+
onTimeLog: (label, duration, ...args) =>
|
|
205
|
+
timeLogCalls.push({ label, duration, args }),
|
|
206
|
+
});
|
|
207
|
+
context.evalSync(`console.timeLog("nonexistent")`);
|
|
208
|
+
assert.strictEqual(timeLogCalls.length, 0);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe("counting methods", () => {
|
|
213
|
+
test("console.count increments counter", async () => {
|
|
214
|
+
const countCalls: Array<{ label: string; count: number }> = [];
|
|
215
|
+
await setupConsole(context, {
|
|
216
|
+
onCount: (label, count) => countCalls.push({ label, count }),
|
|
217
|
+
});
|
|
218
|
+
context.evalSync(`console.count("test")`);
|
|
219
|
+
assert.strictEqual(countCalls.length, 1);
|
|
220
|
+
assert.deepStrictEqual(countCalls[0], { label: "test", count: 1 });
|
|
221
|
+
|
|
222
|
+
context.evalSync(`console.count("test")`);
|
|
223
|
+
assert.strictEqual(countCalls.length, 2);
|
|
224
|
+
assert.deepStrictEqual(countCalls[1], { label: "test", count: 2 });
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("console.count uses default label", async () => {
|
|
228
|
+
const countCalls: Array<{ label: string; count: number }> = [];
|
|
229
|
+
await setupConsole(context, {
|
|
230
|
+
onCount: (label, count) => countCalls.push({ label, count }),
|
|
231
|
+
});
|
|
232
|
+
context.evalSync(`console.count()`);
|
|
233
|
+
assert.strictEqual(countCalls[0].label, "default");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("console.countReset clears counter", async () => {
|
|
237
|
+
const countCalls: Array<{ label: string; count: number }> = [];
|
|
238
|
+
const countResetCalls: string[] = [];
|
|
239
|
+
const handle = await setupConsole(context, {
|
|
240
|
+
onCount: (label, count) => countCalls.push({ label, count }),
|
|
241
|
+
onCountReset: (label) => countResetCalls.push(label),
|
|
242
|
+
});
|
|
243
|
+
context.evalSync(`console.count("test")`);
|
|
244
|
+
context.evalSync(`console.count("test")`);
|
|
245
|
+
context.evalSync(`console.countReset("test")`);
|
|
246
|
+
|
|
247
|
+
assert.deepStrictEqual(countResetCalls, ["test"]);
|
|
248
|
+
assert.strictEqual(handle.getCounters().has("test"), false);
|
|
249
|
+
|
|
250
|
+
// After reset, count starts from 1 again
|
|
251
|
+
context.evalSync(`console.count("test")`);
|
|
252
|
+
assert.deepStrictEqual(countCalls[countCalls.length - 1], {
|
|
253
|
+
label: "test",
|
|
254
|
+
count: 1,
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("console.countReset with default label", async () => {
|
|
259
|
+
const countResetCalls: string[] = [];
|
|
260
|
+
await setupConsole(context, {
|
|
261
|
+
onCountReset: (label) => countResetCalls.push(label),
|
|
262
|
+
});
|
|
263
|
+
context.evalSync(`console.count()`);
|
|
264
|
+
context.evalSync(`console.countReset()`);
|
|
265
|
+
assert.deepStrictEqual(countResetCalls, ["default"]);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
test("console.countReset with non-existent counter still calls handler", async () => {
|
|
269
|
+
const countResetCalls: string[] = [];
|
|
270
|
+
await setupConsole(context, {
|
|
271
|
+
onCountReset: (label) => countResetCalls.push(label),
|
|
272
|
+
});
|
|
273
|
+
context.evalSync(`console.countReset("nonexistent")`);
|
|
274
|
+
assert.deepStrictEqual(countResetCalls, ["nonexistent"]);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("getCounters returns correct state", async () => {
|
|
278
|
+
const handle = await setupConsole(context);
|
|
279
|
+
context.evalSync(`console.count("a")`);
|
|
280
|
+
context.evalSync(`console.count("a")`);
|
|
281
|
+
context.evalSync(`console.count("b")`);
|
|
282
|
+
|
|
283
|
+
const counters = handle.getCounters();
|
|
284
|
+
assert.strictEqual(counters.get("a"), 2);
|
|
285
|
+
assert.strictEqual(counters.get("b"), 1);
|
|
286
|
+
});
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
describe("grouping methods", () => {
|
|
290
|
+
test("console.group increments depth", async () => {
|
|
291
|
+
const groupCalls: Array<{ label: string; collapsed: boolean }> = [];
|
|
292
|
+
const handle = await setupConsole(context, {
|
|
293
|
+
onGroup: (label, collapsed) => groupCalls.push({ label, collapsed }),
|
|
294
|
+
});
|
|
295
|
+
assert.strictEqual(handle.getGroupDepth(), 0);
|
|
296
|
+
context.evalSync(`console.group("Group 1")`);
|
|
297
|
+
assert.strictEqual(handle.getGroupDepth(), 1);
|
|
298
|
+
assert.deepStrictEqual(groupCalls[0], {
|
|
299
|
+
label: "Group 1",
|
|
300
|
+
collapsed: false,
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("console.groupCollapsed increments depth with collapsed flag", async () => {
|
|
305
|
+
const groupCalls: Array<{ label: string; collapsed: boolean }> = [];
|
|
306
|
+
const handle = await setupConsole(context, {
|
|
307
|
+
onGroup: (label, collapsed) => groupCalls.push({ label, collapsed }),
|
|
308
|
+
});
|
|
309
|
+
context.evalSync(`console.groupCollapsed("Collapsed Group")`);
|
|
310
|
+
assert.strictEqual(handle.getGroupDepth(), 1);
|
|
311
|
+
assert.deepStrictEqual(groupCalls[0], {
|
|
312
|
+
label: "Collapsed Group",
|
|
313
|
+
collapsed: true,
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
test("console.group uses default label", async () => {
|
|
318
|
+
const groupCalls: Array<{ label: string; collapsed: boolean }> = [];
|
|
319
|
+
await setupConsole(context, {
|
|
320
|
+
onGroup: (label, collapsed) => groupCalls.push({ label, collapsed }),
|
|
321
|
+
});
|
|
322
|
+
context.evalSync(`console.group()`);
|
|
323
|
+
assert.strictEqual(groupCalls[0].label, "default");
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
test("console.groupEnd decrements depth", async () => {
|
|
327
|
+
let groupEndCalls = 0;
|
|
328
|
+
const handle = await setupConsole(context, {
|
|
329
|
+
onGroupEnd: () => groupEndCalls++,
|
|
330
|
+
});
|
|
331
|
+
context.evalSync(`console.group()`);
|
|
332
|
+
context.evalSync(`console.group()`);
|
|
333
|
+
assert.strictEqual(handle.getGroupDepth(), 2);
|
|
334
|
+
|
|
335
|
+
context.evalSync(`console.groupEnd()`);
|
|
336
|
+
assert.strictEqual(handle.getGroupDepth(), 1);
|
|
337
|
+
assert.strictEqual(groupEndCalls, 1);
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
test("console.groupEnd at depth 0 stays at 0", async () => {
|
|
341
|
+
let groupEndCalls = 0;
|
|
342
|
+
const handle = await setupConsole(context, {
|
|
343
|
+
onGroupEnd: () => groupEndCalls++,
|
|
344
|
+
});
|
|
345
|
+
context.evalSync(`console.groupEnd()`);
|
|
346
|
+
assert.strictEqual(handle.getGroupDepth(), 0);
|
|
347
|
+
assert.strictEqual(groupEndCalls, 1); // Handler still called
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test("nested groups track depth correctly", async () => {
|
|
351
|
+
const handle = await setupConsole(context);
|
|
352
|
+
context.evalSync(`
|
|
353
|
+
console.group("Level 1");
|
|
354
|
+
console.group("Level 2");
|
|
355
|
+
console.group("Level 3");
|
|
356
|
+
`);
|
|
357
|
+
assert.strictEqual(handle.getGroupDepth(), 3);
|
|
358
|
+
|
|
359
|
+
context.evalSync(`
|
|
360
|
+
console.groupEnd();
|
|
361
|
+
console.groupEnd();
|
|
362
|
+
`);
|
|
363
|
+
assert.strictEqual(handle.getGroupDepth(), 1);
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
describe("other methods", () => {
|
|
368
|
+
test("console.clear calls onClear", async () => {
|
|
369
|
+
let clearCalls = 0;
|
|
370
|
+
await setupConsole(context, {
|
|
371
|
+
onClear: () => clearCalls++,
|
|
372
|
+
});
|
|
373
|
+
context.evalSync(`console.clear()`);
|
|
374
|
+
assert.strictEqual(clearCalls, 1);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
test("console.assert with truthy condition does not call handler", async () => {
|
|
378
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
379
|
+
await setupConsole(context, {
|
|
380
|
+
onAssert: (condition, ...args) =>
|
|
381
|
+
assertCalls.push({ condition, args }),
|
|
382
|
+
});
|
|
383
|
+
context.evalSync(`console.assert(true, "should not appear")`);
|
|
384
|
+
assert.strictEqual(assertCalls.length, 0);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
test("console.assert with falsy condition calls handler", async () => {
|
|
388
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
389
|
+
await setupConsole(context, {
|
|
390
|
+
onAssert: (condition, ...args) =>
|
|
391
|
+
assertCalls.push({ condition, args }),
|
|
392
|
+
});
|
|
393
|
+
context.evalSync(`console.assert(false, "assertion failed", 123)`);
|
|
394
|
+
assert.strictEqual(assertCalls.length, 1);
|
|
395
|
+
assert.deepStrictEqual(assertCalls[0], {
|
|
396
|
+
condition: false,
|
|
397
|
+
args: ["assertion failed", 123],
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
test("console.assert with undefined condition calls handler", async () => {
|
|
402
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
403
|
+
await setupConsole(context, {
|
|
404
|
+
onAssert: (condition, ...args) =>
|
|
405
|
+
assertCalls.push({ condition, args }),
|
|
406
|
+
});
|
|
407
|
+
context.evalSync(`console.assert(undefined)`);
|
|
408
|
+
assert.strictEqual(assertCalls.length, 1);
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
test("console.assert with 0 calls handler", async () => {
|
|
412
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
413
|
+
await setupConsole(context, {
|
|
414
|
+
onAssert: (condition, ...args) =>
|
|
415
|
+
assertCalls.push({ condition, args }),
|
|
416
|
+
});
|
|
417
|
+
context.evalSync(`console.assert(0)`);
|
|
418
|
+
assert.strictEqual(assertCalls.length, 1);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
test("console.assert with empty string calls handler", async () => {
|
|
422
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
423
|
+
await setupConsole(context, {
|
|
424
|
+
onAssert: (condition, ...args) =>
|
|
425
|
+
assertCalls.push({ condition, args }),
|
|
426
|
+
});
|
|
427
|
+
context.evalSync(`console.assert("")`);
|
|
428
|
+
assert.strictEqual(assertCalls.length, 1);
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
test("console.assert with null calls handler", async () => {
|
|
432
|
+
const assertCalls: Array<{ condition: boolean; args: unknown[] }> = [];
|
|
433
|
+
await setupConsole(context, {
|
|
434
|
+
onAssert: (condition, ...args) =>
|
|
435
|
+
assertCalls.push({ condition, args }),
|
|
436
|
+
});
|
|
437
|
+
context.evalSync(`console.assert(null)`);
|
|
438
|
+
assert.strictEqual(assertCalls.length, 1);
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe("handle methods", () => {
|
|
443
|
+
test("reset() clears all state", async () => {
|
|
444
|
+
const handle = await setupConsole(context);
|
|
445
|
+
context.evalSync(`
|
|
446
|
+
console.time("timer");
|
|
447
|
+
console.count("counter");
|
|
448
|
+
console.group("group");
|
|
449
|
+
`);
|
|
450
|
+
|
|
451
|
+
assert.strictEqual(handle.getTimers().size, 1);
|
|
452
|
+
assert.strictEqual(handle.getCounters().size, 1);
|
|
453
|
+
assert.strictEqual(handle.getGroupDepth(), 1);
|
|
454
|
+
|
|
455
|
+
handle.reset();
|
|
456
|
+
|
|
457
|
+
assert.strictEqual(handle.getTimers().size, 0);
|
|
458
|
+
assert.strictEqual(handle.getCounters().size, 0);
|
|
459
|
+
assert.strictEqual(handle.getGroupDepth(), 0);
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
test("getTimers returns a copy", async () => {
|
|
463
|
+
const handle = await setupConsole(context);
|
|
464
|
+
context.evalSync(`console.time("test")`);
|
|
465
|
+
const timers1 = handle.getTimers();
|
|
466
|
+
const timers2 = handle.getTimers();
|
|
467
|
+
assert.notStrictEqual(timers1, timers2);
|
|
468
|
+
assert.deepStrictEqual([...timers1.keys()], [...timers2.keys()]);
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
test("getCounters returns a copy", async () => {
|
|
472
|
+
const handle = await setupConsole(context);
|
|
473
|
+
context.evalSync(`console.count("test")`);
|
|
474
|
+
const counters1 = handle.getCounters();
|
|
475
|
+
const counters2 = handle.getCounters();
|
|
476
|
+
assert.notStrictEqual(counters1, counters2);
|
|
477
|
+
assert.deepStrictEqual(counters1, counters2);
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
describe("no handlers", () => {
|
|
482
|
+
test("works without handlers", async () => {
|
|
483
|
+
// Create a new context without handlers
|
|
484
|
+
const testIsolate = new ivm.Isolate();
|
|
485
|
+
const testContext = await testIsolate.createContext();
|
|
486
|
+
|
|
487
|
+
const h = await setupConsole(testContext); // No handlers
|
|
488
|
+
|
|
489
|
+
const result = testContext.evalSync(`
|
|
490
|
+
console.log("test");
|
|
491
|
+
console.time("t");
|
|
492
|
+
console.timeEnd("t");
|
|
493
|
+
console.count("c");
|
|
494
|
+
console.countReset("c");
|
|
495
|
+
console.clear();
|
|
496
|
+
console.assert(false);
|
|
497
|
+
console.group("g");
|
|
498
|
+
console.groupEnd();
|
|
499
|
+
"done"
|
|
500
|
+
`);
|
|
501
|
+
|
|
502
|
+
assert.strictEqual(result, "done");
|
|
503
|
+
|
|
504
|
+
h.dispose();
|
|
505
|
+
testContext.release();
|
|
506
|
+
testIsolate.dispose();
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
});
|
package/tsconfig.json
ADDED
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
# @ricsam/isolate-console
|
|
2
|
-
|
|
3
|
-
## ⚠️ IMPORTANT NOTICE ⚠️
|
|
4
|
-
|
|
5
|
-
**This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
|
|
6
|
-
|
|
7
|
-
This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
|
|
8
|
-
|
|
9
|
-
## Purpose
|
|
10
|
-
|
|
11
|
-
This package exists to:
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@ricsam/isolate-console`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
15
|
-
|
|
16
|
-
## What is OIDC Trusted Publishing?
|
|
17
|
-
|
|
18
|
-
OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
|
|
19
|
-
|
|
20
|
-
## Setup Instructions
|
|
21
|
-
|
|
22
|
-
To properly configure OIDC trusted publishing for this package:
|
|
23
|
-
|
|
24
|
-
1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
28
|
-
|
|
29
|
-
## DO NOT USE THIS PACKAGE
|
|
30
|
-
|
|
31
|
-
This package is a placeholder for OIDC configuration only. It:
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
36
|
-
|
|
37
|
-
## More Information
|
|
38
|
-
|
|
39
|
-
For more details about npm's trusted publishing feature, see:
|
|
40
|
-
- [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
|
|
41
|
-
- [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
**Maintained for OIDC setup purposes only**
|