@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 ADDED
@@ -0,0 +1,9 @@
1
+ # @ricsam/isolate-console
2
+
3
+ ## 0.1.1
4
+
5
+ ### Patch Changes
6
+
7
+ - initial release
8
+ - Updated dependencies
9
+ - @ricsam/isolate-core@0.1.1
package/package.json CHANGED
@@ -1,10 +1,29 @@
1
1
  {
2
2
  "name": "@ricsam/isolate-console",
3
- "version": "0.0.1",
4
- "description": "OIDC trusted publishing setup package for @ricsam/isolate-console",
5
- "keywords": [
6
- "oidc",
7
- "trusted-publishing",
8
- "setup"
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
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "rootDir": "./src"
5
+ },
6
+ "include": ["src/**/*"],
7
+ "exclude": ["node_modules", "dist", "**/*.test.ts"]
8
+ }
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**