@nimbus-sh/core 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -7
- package/dist/_shared/error-text.d.ts +15 -0
- package/dist/_shared/error-text.d.ts.map +1 -0
- package/dist/_shared/error-text.js +21 -0
- package/dist/_shared/exports-resolver.d.ts +19 -7
- package/dist/_shared/exports-resolver.d.ts.map +1 -1
- package/dist/_shared/exports-resolver.js +1 -1
- package/dist/_shared/retry.d.ts.map +1 -1
- package/dist/_shared/retry.js +3 -1
- package/dist/_shared/w7-frame.js +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +30 -16
- package/dist/observability/oom-classify.d.ts +1 -1
- package/dist/observability/oom-classify.d.ts.map +1 -1
- package/dist/observability/oom-classify.js +7 -0
- package/dist/observability/oom-discriminator.d.ts +1 -1
- package/dist/runtime/esbuild-service.d.ts +4 -3
- package/dist/runtime/esbuild-service.d.ts.map +1 -1
- package/dist/runtime/esbuild-service.js +9 -4
- package/dist/runtime/installed-runtimes.d.ts +4 -3
- package/dist/runtime/installed-runtimes.d.ts.map +1 -1
- package/dist/runtime/long-running-handle.d.ts +3 -3
- package/dist/runtime/long-running-handle.d.ts.map +1 -1
- package/dist/runtime/os-contracts.d.ts +7 -5
- package/dist/runtime/os-contracts.d.ts.map +1 -1
- package/dist/runtime/port-registry.d.ts +28 -0
- package/dist/runtime/port-registry.d.ts.map +1 -1
- package/dist/runtime/port-registry.js +79 -9
- package/dist/runtime/process-logs.d.ts +9 -1
- package/dist/runtime/process-logs.d.ts.map +1 -1
- package/dist/runtime/project-detect.d.ts +6 -1
- package/dist/runtime/project-detect.d.ts.map +1 -1
- package/dist/runtime/require-resolver.d.ts.map +1 -1
- package/dist/runtime/require-resolver.js +3 -4
- package/dist/runtime/runtime-registry.d.ts +4 -3
- package/dist/runtime/runtime-registry.d.ts.map +1 -1
- package/dist/runtime/runtime-registry.js +2 -1
- package/dist/runtime/sqlite-runtime-fs-bridge.js +1 -5
- package/dist/runtime/virtual-socket-kernel.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.d.ts.map +1 -1
- package/dist/runtime/virtual-socket-kernel.generated.js +2 -2
- package/dist/runtime/virtual-socket-kernel.js +10 -5
- package/dist/runtime/wasm-runner.d.ts.map +1 -1
- package/dist/runtime/wasm-runner.js +22 -14
- package/dist/shell/features.d.ts.map +1 -1
- package/dist/shell/features.js +5 -4
- package/dist/shell/unix-commands.d.ts +14 -1
- package/dist/shell/unix-commands.d.ts.map +1 -1
- package/dist/shell/unix-commands.js +115 -69
- package/dist/substrate/lifo/commands/net/ip.d.ts.map +1 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts +2 -1
- package/dist/substrate/lifo/commands/system/ps.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/ServiceManager.js +0 -2
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts +22 -11
- package/dist/substrate/lifo/kernel/network/NetworkStack.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/NetworkStack.js +0 -2
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts +2 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.js +3 -4
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts +17 -0
- package/dist/substrate/lifo/kernel/vfs/VFS.d.ts.map +1 -1
- package/dist/substrate/lifo/kernel/vfs/VFS.js +33 -0
- package/dist/substrate/lifo/node-compat/esbuild.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/esbuild.js +2 -4
- package/dist/substrate/lifo/node-compat/index.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/index.js +3 -2
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts +19 -0
- package/dist/substrate/lifo/node-compat/loose-equality.d.ts.map +1 -0
- package/dist/substrate/lifo/node-compat/loose-equality.js +91 -0
- package/dist/substrate/lifo/node-compat/process.d.ts.map +1 -1
- package/dist/substrate/lifo/node-compat/process.js +6 -3
- package/dist/substrate/lifo/shell/Shell.d.ts +19 -0
- package/dist/substrate/lifo/shell/Shell.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/Shell.js +86 -75
- package/dist/substrate/lifo/shell/lexer.d.ts +9 -0
- package/dist/substrate/lifo/shell/lexer.d.ts.map +1 -1
- package/dist/substrate/lifo/shell/lexer.js +40 -0
- package/dist/vfs/seed-project.d.ts.map +1 -1
- package/dist/vfs/seed-project.js +8 -2
- package/dist/vfs/sqlite-vfs.d.ts +61 -1
- package/dist/vfs/sqlite-vfs.d.ts.map +1 -1
- package/dist/vfs/sqlite-vfs.js +199 -35
- package/package.json +1 -1
- package/src/_shared/error-text.ts +21 -0
- package/src/_shared/exports-resolver.ts +24 -4
- package/src/_shared/retry.ts +4 -2
- package/src/_shared/w7-frame.ts +2 -1
- package/src/constants.ts +30 -16
- package/src/observability/oom-classify.ts +7 -0
- package/src/observability/oom-discriminator.ts +1 -1
- package/src/runtime/esbuild-service.ts +16 -11
- package/src/runtime/installed-runtimes.ts +4 -3
- package/src/runtime/long-running-handle.ts +3 -3
- package/src/runtime/os-contracts.ts +7 -5
- package/src/runtime/port-registry.ts +97 -9
- package/src/runtime/process-logs.ts +12 -3
- package/src/runtime/project-detect.ts +7 -1
- package/src/runtime/require-resolver.ts +7 -7
- package/src/runtime/runtime-registry.ts +9 -7
- package/src/runtime/sqlite-runtime-fs-bridge.ts +9 -6
- package/src/runtime/virtual-socket-kernel.generated.ts +2 -2
- package/src/runtime/virtual-socket-kernel.ts +10 -5
- package/src/runtime/wasm-runner.ts +55 -29
- package/src/shell/features.ts +5 -4
- package/src/shell/unix-commands.ts +273 -128
- package/src/substrate/lifo/commands/net/ip.ts +15 -15
- package/src/substrate/lifo/commands/system/ps.ts +2 -1
- package/src/substrate/lifo/kernel/ServiceManager.ts +0 -2
- package/src/substrate/lifo/kernel/network/NetworkStack.ts +24 -17
- package/src/substrate/lifo/kernel/network/tunnel/WebSocketTunnel.ts +37 -12
- package/src/substrate/lifo/kernel/vfs/VFS.ts +34 -0
- package/src/substrate/lifo/node-compat/esbuild.ts +16 -8
- package/src/substrate/lifo/node-compat/index.ts +3 -2
- package/src/substrate/lifo/node-compat/loose-equality.ts +81 -0
- package/src/substrate/lifo/node-compat/process.ts +5 -2
- package/src/substrate/lifo/shell/Shell.ts +92 -70
- package/src/substrate/lifo/shell/lexer.ts +38 -0
- package/src/vfs/seed-project.ts +10 -3
- package/src/vfs/sqlite-vfs.ts +209 -36
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Command } from '../types.js';
|
|
1
|
+
import type { Command, CommandContext } from '../types.js';
|
|
2
2
|
import type { NetworkStack } from '../../kernel/network/index.js';
|
|
3
3
|
import type { Kernel } from '../../kernel/index.js';
|
|
4
4
|
import { VETHPair } from '../../kernel/network/tunnel/VETHPair.js';
|
|
@@ -65,7 +65,7 @@ Examples:
|
|
|
65
65
|
// ═══════════════════════════════════════════════════════════════
|
|
66
66
|
|
|
67
67
|
async function handleLink(
|
|
68
|
-
ctx:
|
|
68
|
+
ctx: CommandContext,
|
|
69
69
|
command: string,
|
|
70
70
|
args: string[],
|
|
71
71
|
networkStack: NetworkStack
|
|
@@ -108,7 +108,7 @@ Examples:
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
async function linkShow(ctx:
|
|
111
|
+
async function linkShow(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
112
112
|
let targetInterface: string | undefined;
|
|
113
113
|
|
|
114
114
|
// Parse "dev NAME"
|
|
@@ -154,7 +154,7 @@ async function linkShow(ctx: any, args: string[], networkStack: NetworkStack): P
|
|
|
154
154
|
return 0;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
async function linkAdd(ctx:
|
|
157
|
+
async function linkAdd(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
158
158
|
if (args.length < 3) {
|
|
159
159
|
ctx.stderr.write('Usage: ip link add NAME type TYPE [ OPTIONS ]\n');
|
|
160
160
|
return 1;
|
|
@@ -207,7 +207,7 @@ async function linkAdd(ctx: any, args: string[], networkStack: NetworkStack): Pr
|
|
|
207
207
|
return 1;
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
async function linkDelete(ctx:
|
|
210
|
+
async function linkDelete(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
211
211
|
if (args.length < 1) {
|
|
212
212
|
ctx.stderr.write('Usage: ip link del NAME\n');
|
|
213
213
|
return 1;
|
|
@@ -243,7 +243,7 @@ async function linkDelete(ctx: any, args: string[], networkStack: NetworkStack):
|
|
|
243
243
|
return 1;
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
async function linkSet(ctx:
|
|
246
|
+
async function linkSet(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
247
247
|
if (args.length < 2) {
|
|
248
248
|
ctx.stderr.write('Usage: ip link set NAME { up | down | netns NETNS }\n');
|
|
249
249
|
return 1;
|
|
@@ -362,7 +362,7 @@ async function linkSet(ctx: any, args: string[], networkStack: NetworkStack): Pr
|
|
|
362
362
|
// ═══════════════════════════════════════════════════════════════
|
|
363
363
|
|
|
364
364
|
async function handleAddr(
|
|
365
|
-
ctx:
|
|
365
|
+
ctx: CommandContext,
|
|
366
366
|
command: string,
|
|
367
367
|
_args: string[],
|
|
368
368
|
_networkStack: NetworkStack
|
|
@@ -393,7 +393,7 @@ Examples:
|
|
|
393
393
|
// ═══════════════════════════════════════════════════════════════
|
|
394
394
|
|
|
395
395
|
async function handleRoute(
|
|
396
|
-
ctx:
|
|
396
|
+
ctx: CommandContext,
|
|
397
397
|
command: string,
|
|
398
398
|
_args: string[],
|
|
399
399
|
_networkStack: NetworkStack
|
|
@@ -426,7 +426,7 @@ Examples:
|
|
|
426
426
|
// ═══════════════════════════════════════════════════════════════
|
|
427
427
|
|
|
428
428
|
async function handleNetns(
|
|
429
|
-
ctx:
|
|
429
|
+
ctx: CommandContext,
|
|
430
430
|
command: string,
|
|
431
431
|
args: string[],
|
|
432
432
|
networkStack: NetworkStack
|
|
@@ -458,7 +458,7 @@ Examples:
|
|
|
458
458
|
}
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
async function netnsShow(ctx:
|
|
461
|
+
async function netnsShow(ctx: CommandContext, _args: string[], networkStack: NetworkStack): Promise<number> {
|
|
462
462
|
const namespaces = networkStack.getAllNamespaces();
|
|
463
463
|
|
|
464
464
|
for (const ns of namespaces) {
|
|
@@ -468,7 +468,7 @@ async function netnsShow(ctx: any, _args: string[], networkStack: NetworkStack):
|
|
|
468
468
|
return 0;
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
-
async function netnsAdd(ctx:
|
|
471
|
+
async function netnsAdd(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
472
472
|
if (args.length < 1) {
|
|
473
473
|
ctx.stderr.write('Usage: ip netns add NAME\n');
|
|
474
474
|
return 1;
|
|
@@ -487,7 +487,7 @@ async function netnsAdd(ctx: any, args: string[], networkStack: NetworkStack): P
|
|
|
487
487
|
}
|
|
488
488
|
}
|
|
489
489
|
|
|
490
|
-
async function netnsDelete(ctx:
|
|
490
|
+
async function netnsDelete(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
491
491
|
if (args.length < 1) {
|
|
492
492
|
ctx.stderr.write('Usage: ip netns del NAME\n');
|
|
493
493
|
return 1;
|
|
@@ -520,7 +520,7 @@ async function netnsDelete(ctx: any, args: string[], networkStack: NetworkStack)
|
|
|
520
520
|
// ═══════════════════════════════════════════════════════════════
|
|
521
521
|
|
|
522
522
|
async function handleBridge(
|
|
523
|
-
ctx:
|
|
523
|
+
ctx: CommandContext,
|
|
524
524
|
command: string,
|
|
525
525
|
args: string[],
|
|
526
526
|
networkStack: NetworkStack
|
|
@@ -547,7 +547,7 @@ Examples:
|
|
|
547
547
|
}
|
|
548
548
|
}
|
|
549
549
|
|
|
550
|
-
async function bridgeFdb(ctx:
|
|
550
|
+
async function bridgeFdb(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
551
551
|
const command = args[0] || 'show';
|
|
552
552
|
|
|
553
553
|
if (command !== 'show') {
|
|
@@ -585,7 +585,7 @@ async function bridgeFdb(ctx: any, args: string[], networkStack: NetworkStack):
|
|
|
585
585
|
return 0;
|
|
586
586
|
}
|
|
587
587
|
|
|
588
|
-
async function bridgeLink(ctx:
|
|
588
|
+
async function bridgeLink(ctx: CommandContext, args: string[], networkStack: NetworkStack): Promise<number> {
|
|
589
589
|
const command = args[0] || 'show';
|
|
590
590
|
|
|
591
591
|
if (command !== 'show') {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Command } from '../types.js';
|
|
2
2
|
import type { ProcessRegistry } from '../../shell/ProcessRegistry.js';
|
|
3
|
+
import type { JobTable } from '../../shell/jobs.js';
|
|
3
4
|
|
|
4
5
|
export function createPsCommand(processRegistry: ProcessRegistry): Command {
|
|
5
6
|
return async (ctx) => {
|
|
@@ -20,7 +21,7 @@ export function createPsCommand(processRegistry: ProcessRegistry): Command {
|
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
// Legacy function for backward compatibility
|
|
23
|
-
export function createPsCommandFromJobTable(jobTable:
|
|
24
|
+
export function createPsCommandFromJobTable(jobTable: JobTable): Command {
|
|
24
25
|
return async (ctx) => {
|
|
25
26
|
ctx.stdout.write(' PID TTY TIME CMD\n');
|
|
26
27
|
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
RouteEntry,
|
|
12
12
|
NetworkTunnel,
|
|
13
13
|
} from './types.js';
|
|
14
|
+
import type { VETHPair } from './tunnel/VETHPair.js';
|
|
15
|
+
import type { Bridge } from './Bridge.js';
|
|
14
16
|
|
|
15
17
|
/** Port binding with concrete Socket type */
|
|
16
18
|
interface PortBinding {
|
|
@@ -21,6 +23,13 @@ interface PortBinding {
|
|
|
21
23
|
namespace: string;
|
|
22
24
|
}
|
|
23
25
|
|
|
26
|
+
/**
|
|
27
|
+
* A tunnel as the stack holds it. `NetworkTunnel` declares its interface as the
|
|
28
|
+
* plain device record; namespaces hold the concrete device, which is what every
|
|
29
|
+
* tunnel implementation exposes (see `BaseTunnel`).
|
|
30
|
+
*/
|
|
31
|
+
type BoundTunnel = Omit<NetworkTunnel, 'interface'> & { interface: NetworkInterface };
|
|
32
|
+
|
|
24
33
|
/**
|
|
25
34
|
* Virtual network stack
|
|
26
35
|
* Provides Linux-like networking with interfaces, routing, sockets, DNS, and tunneling
|
|
@@ -33,9 +42,9 @@ export class NetworkStack {
|
|
|
33
42
|
private nextNamespaceId = 1;
|
|
34
43
|
|
|
35
44
|
// Tunnel management
|
|
36
|
-
private tunnels = new Map<string,
|
|
37
|
-
private vethPairs = new Map<string,
|
|
38
|
-
private bridges = new Map<string,
|
|
45
|
+
private tunnels = new Map<string, BoundTunnel>(); // tunnel name -> tunnel
|
|
46
|
+
private vethPairs = new Map<string, VETHPair>(); // veth pair id -> VETHPair
|
|
47
|
+
private bridges = new Map<string, Bridge>(); // bridge name -> Bridge
|
|
39
48
|
private nextTunnelId = 0;
|
|
40
49
|
|
|
41
50
|
constructor() {
|
|
@@ -396,18 +405,16 @@ export class NetworkStack {
|
|
|
396
405
|
/**
|
|
397
406
|
* Add tunnel to network stack
|
|
398
407
|
*/
|
|
399
|
-
addTunnel(name: string, tunnel:
|
|
408
|
+
addTunnel(name: string, tunnel: BoundTunnel): void {
|
|
400
409
|
if (this.tunnels.has(name)) {
|
|
401
410
|
throw new Error(`Tunnel ${name} already exists`);
|
|
402
411
|
}
|
|
403
412
|
|
|
404
413
|
this.tunnels.set(name, tunnel);
|
|
405
414
|
|
|
406
|
-
// Add tunnel interface to namespace
|
|
407
|
-
// Cast to NetworkInterface class since tunnel.interface is the concrete class
|
|
408
415
|
const ns = this.namespaces.get(tunnel.interface.namespace);
|
|
409
416
|
if (ns) {
|
|
410
|
-
ns.addInterface(tunnel.interface
|
|
417
|
+
ns.addInterface(tunnel.interface);
|
|
411
418
|
}
|
|
412
419
|
}
|
|
413
420
|
|
|
@@ -435,28 +442,28 @@ export class NetworkStack {
|
|
|
435
442
|
/**
|
|
436
443
|
* Get tunnel by name
|
|
437
444
|
*/
|
|
438
|
-
getTunnel(name: string):
|
|
445
|
+
getTunnel(name: string): BoundTunnel | undefined {
|
|
439
446
|
return this.tunnels.get(name);
|
|
440
447
|
}
|
|
441
448
|
|
|
442
449
|
/**
|
|
443
450
|
* Get all tunnels
|
|
444
451
|
*/
|
|
445
|
-
getAllTunnels():
|
|
452
|
+
getAllTunnels(): BoundTunnel[] {
|
|
446
453
|
return Array.from(this.tunnels.values());
|
|
447
454
|
}
|
|
448
455
|
|
|
449
456
|
/**
|
|
450
457
|
* Get tunnels by namespace
|
|
451
458
|
*/
|
|
452
|
-
getTunnelsByNamespace(namespace: string):
|
|
459
|
+
getTunnelsByNamespace(namespace: string): BoundTunnel[] {
|
|
453
460
|
return this.getAllTunnels().filter((t) => t.interface.namespace === namespace);
|
|
454
461
|
}
|
|
455
462
|
|
|
456
463
|
/**
|
|
457
464
|
* Add VETH pair
|
|
458
465
|
*/
|
|
459
|
-
addVETHPair(id: string, vethPair:
|
|
466
|
+
addVETHPair(id: string, vethPair: VETHPair): void {
|
|
460
467
|
if (this.vethPairs.has(id)) {
|
|
461
468
|
throw new Error(`VETH pair ${id} already exists`);
|
|
462
469
|
}
|
|
@@ -496,7 +503,7 @@ export class NetworkStack {
|
|
|
496
503
|
/**
|
|
497
504
|
* Get VETH pair by ID or interface name
|
|
498
505
|
*/
|
|
499
|
-
getVETHPair(idOrName: string):
|
|
506
|
+
getVETHPair(idOrName: string): VETHPair | undefined {
|
|
500
507
|
// Try by ID first
|
|
501
508
|
const byId = this.vethPairs.get(idOrName);
|
|
502
509
|
if (byId) return byId;
|
|
@@ -514,7 +521,7 @@ export class NetworkStack {
|
|
|
514
521
|
/**
|
|
515
522
|
* Get all VETH pairs
|
|
516
523
|
*/
|
|
517
|
-
getAllVETHPairs():
|
|
524
|
+
getAllVETHPairs(): VETHPair[] {
|
|
518
525
|
return Array.from(this.vethPairs.values());
|
|
519
526
|
}
|
|
520
527
|
|
|
@@ -532,7 +539,7 @@ export class NetworkStack {
|
|
|
532
539
|
/**
|
|
533
540
|
* Add bridge to network stack
|
|
534
541
|
*/
|
|
535
|
-
addBridge(name: string, bridge:
|
|
542
|
+
addBridge(name: string, bridge: Bridge): void {
|
|
536
543
|
if (this.bridges.has(name)) {
|
|
537
544
|
throw new Error(`Bridge ${name} already exists`);
|
|
538
545
|
}
|
|
@@ -570,21 +577,21 @@ export class NetworkStack {
|
|
|
570
577
|
/**
|
|
571
578
|
* Get bridge by name
|
|
572
579
|
*/
|
|
573
|
-
getBridge(name: string):
|
|
580
|
+
getBridge(name: string): Bridge | undefined {
|
|
574
581
|
return this.bridges.get(name);
|
|
575
582
|
}
|
|
576
583
|
|
|
577
584
|
/**
|
|
578
585
|
* Get all bridges
|
|
579
586
|
*/
|
|
580
|
-
getAllBridges():
|
|
587
|
+
getAllBridges(): Bridge[] {
|
|
581
588
|
return Array.from(this.bridges.values());
|
|
582
589
|
}
|
|
583
590
|
|
|
584
591
|
/**
|
|
585
592
|
* Get bridges by namespace
|
|
586
593
|
*/
|
|
587
|
-
getBridgesByNamespace(namespace: string):
|
|
594
|
+
getBridgesByNamespace(namespace: string): Bridge[] {
|
|
588
595
|
return this.getAllBridges().filter((b) => b.interface.namespace === namespace);
|
|
589
596
|
}
|
|
590
597
|
}
|
|
@@ -3,6 +3,33 @@ import type { NetworkStack } from '../NetworkStack.js';
|
|
|
3
3
|
import { BaseTunnel } from './BaseTunnel.js';
|
|
4
4
|
import { Buffer } from '../../../node-compat/buffer.js';
|
|
5
5
|
import type { VirtualResponseWithDone } from '../../../node-compat/http.js';
|
|
6
|
+
import type { VirtualRequestHandler } from '../../index.js';
|
|
7
|
+
|
|
8
|
+
/** Payload a WebSocket `message` event can carry. */
|
|
9
|
+
type WebSocketData = string | ArrayBufferLike | ArrayBufferView | Blob;
|
|
10
|
+
|
|
11
|
+
/** HTTP request forwarded to us by the tunnel server. */
|
|
12
|
+
interface TunnelRequestMessage {
|
|
13
|
+
type: 'request';
|
|
14
|
+
requestId: string;
|
|
15
|
+
method: string;
|
|
16
|
+
url: string;
|
|
17
|
+
headers: Record<string, string>;
|
|
18
|
+
/** base64-encoded */
|
|
19
|
+
body: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** HTTP response carried over the tunnel, in either direction. */
|
|
23
|
+
interface TunnelResponseMessage {
|
|
24
|
+
type: 'response';
|
|
25
|
+
requestId: string;
|
|
26
|
+
statusCode: number;
|
|
27
|
+
headers: Record<string, string>;
|
|
28
|
+
/** base64-encoded */
|
|
29
|
+
body: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type TunnelMessage = TunnelRequestMessage | TunnelResponseMessage;
|
|
6
33
|
|
|
7
34
|
/**
|
|
8
35
|
* WebSocket Tunnel - Bridge virtual network to external WebSocket server
|
|
@@ -21,9 +48,9 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
21
48
|
|
|
22
49
|
private wsUrl: string;
|
|
23
50
|
private ws: WebSocket | null = null;
|
|
24
|
-
private portRegistry?: Map<number,
|
|
51
|
+
private portRegistry?: Map<number, VirtualRequestHandler>;
|
|
25
52
|
private defaultPort: number | null = null;
|
|
26
|
-
private reconnectTimer?:
|
|
53
|
+
private reconnectTimer?: ReturnType<typeof setTimeout>;
|
|
27
54
|
private isReconnecting = false;
|
|
28
55
|
|
|
29
56
|
// Packet queue
|
|
@@ -34,7 +61,7 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
34
61
|
id: string,
|
|
35
62
|
wsUrl: string,
|
|
36
63
|
networkStack: NetworkStack,
|
|
37
|
-
portRegistry?: Map<number,
|
|
64
|
+
portRegistry?: Map<number, VirtualRequestHandler>,
|
|
38
65
|
namespace = 'default',
|
|
39
66
|
defaultPort: number | null = null
|
|
40
67
|
) {
|
|
@@ -201,9 +228,9 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
201
228
|
/**
|
|
202
229
|
* Handle incoming WebSocket message
|
|
203
230
|
*/
|
|
204
|
-
private handleMessage(data:
|
|
231
|
+
private handleMessage(data: WebSocketData): void {
|
|
205
232
|
try {
|
|
206
|
-
const message = JSON.parse(data.toString());
|
|
233
|
+
const message = JSON.parse(data.toString()) as TunnelMessage;
|
|
207
234
|
|
|
208
235
|
if (message.type === 'request') {
|
|
209
236
|
// Handle HTTP request from tunnel server
|
|
@@ -220,7 +247,7 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
220
247
|
/**
|
|
221
248
|
* Handle HTTP request from tunnel server
|
|
222
249
|
*/
|
|
223
|
-
private async handleHttpRequest(message:
|
|
250
|
+
private async handleHttpRequest(message: TunnelRequestMessage): Promise<void> {
|
|
224
251
|
const { requestId, method, url, headers, body } = message;
|
|
225
252
|
|
|
226
253
|
let port: number;
|
|
@@ -243,15 +270,13 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
243
270
|
path = match[2] || '/';
|
|
244
271
|
}
|
|
245
272
|
|
|
246
|
-
// Check if
|
|
247
|
-
|
|
273
|
+
// Check if a server is listening on the port
|
|
274
|
+
const handler = this.portRegistry?.get(port);
|
|
275
|
+
if (!handler) {
|
|
248
276
|
this.sendError(requestId, 404, `No server listening on port ${port}`);
|
|
249
277
|
return;
|
|
250
278
|
}
|
|
251
279
|
|
|
252
|
-
// Get handler
|
|
253
|
-
const handler = this.portRegistry.get(port);
|
|
254
|
-
|
|
255
280
|
// Create virtual request/response
|
|
256
281
|
const vReq = {
|
|
257
282
|
method,
|
|
@@ -298,7 +323,7 @@ export class WebSocketTunnel extends BaseTunnel {
|
|
|
298
323
|
/**
|
|
299
324
|
* Handle HTTP response (for client-side requests)
|
|
300
325
|
*/
|
|
301
|
-
private handleHttpResponse(_message:
|
|
326
|
+
private handleHttpResponse(_message: TunnelResponseMessage): void {
|
|
302
327
|
// For future client-side request support
|
|
303
328
|
// Not needed for current server-only implementation
|
|
304
329
|
}
|
|
@@ -436,6 +436,40 @@ export class VFS {
|
|
|
436
436
|
}
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
+
/**
|
|
440
|
+
* Type probes, mount-aware through {@link stat}.
|
|
441
|
+
*
|
|
442
|
+
* `CredentialedVfs` declares these and the durable coreutils call them on
|
|
443
|
+
* whatever `ctx.vfs` is, so a view that omitted them was not merely missing
|
|
444
|
+
* a convenience: `touch` on an existing file died with
|
|
445
|
+
* `targetVfs.isDirectory is not a function`, because `&&` short-circuited
|
|
446
|
+
* past the call whenever the file was absent — which is why creating a file
|
|
447
|
+
* worked and touching one did not.
|
|
448
|
+
*
|
|
449
|
+
* A structural miss answers false, the way `fs.existsSync` does. A denial
|
|
450
|
+
* still throws: traverse-x enforcement must not be maskable into a quiet
|
|
451
|
+
* false, which is the rule `SqliteVFS.probeInode` already follows.
|
|
452
|
+
*/
|
|
453
|
+
isDirectory(path: string): boolean {
|
|
454
|
+
return this.probeType(path) === 'directory';
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
isFile(path: string): boolean {
|
|
458
|
+
return this.probeType(path) === 'file';
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private probeType(path: string): string | undefined {
|
|
462
|
+
try {
|
|
463
|
+
return this.stat(path).type;
|
|
464
|
+
} catch (error) {
|
|
465
|
+
if (error !== null && typeof error === 'object' && 'code' in error) {
|
|
466
|
+
const code = error.code;
|
|
467
|
+
if (code === 'ENOENT' || code === 'ENOTDIR') return undefined;
|
|
468
|
+
}
|
|
469
|
+
throw error;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
439
473
|
access(path: string, mode: number): void {
|
|
440
474
|
const vp = this.getProvider(path);
|
|
441
475
|
if (vp) {
|
|
@@ -11,28 +11,36 @@ const ESBUILD_WASM_VERSION = '0.24.2';
|
|
|
11
11
|
const ESBUILD_WASM_URL = `https://unpkg.com/esbuild-wasm@${ESBUILD_WASM_VERSION}/esbuild.wasm`;
|
|
12
12
|
const ESBUILD_ESM_URL = `https://unpkg.com/esbuild-wasm@${ESBUILD_WASM_VERSION}/esm/browser.min.js`;
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
/** The esbuild-wasm surface this shim forwards to. */
|
|
15
|
+
interface EsbuildWasm {
|
|
16
|
+
initialize(options: { wasmURL: string }): Promise<void>;
|
|
17
|
+
transform(code: string, options?: unknown): Promise<unknown>;
|
|
18
|
+
build(options?: unknown): Promise<unknown>;
|
|
19
|
+
formatMessages(messages: unknown, options?: unknown): Promise<unknown>;
|
|
20
|
+
analyzeMetafile(metafile: unknown, options?: unknown): Promise<unknown>;
|
|
21
|
+
context(options?: unknown): Promise<unknown>;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let esbuildModule: EsbuildWasm | null = null;
|
|
25
|
+
let initPromise: Promise<EsbuildWasm> | null = null;
|
|
17
26
|
|
|
18
|
-
|
|
19
|
-
async function ensureInitialized(): Promise<any> {
|
|
27
|
+
async function ensureInitialized(): Promise<EsbuildWasm> {
|
|
20
28
|
if (esbuildModule) return esbuildModule;
|
|
21
29
|
|
|
22
30
|
if (!initPromise) {
|
|
23
31
|
initPromise = (async () => {
|
|
24
32
|
// Use dynamic import from CDN
|
|
25
33
|
// This works in browsers natively
|
|
26
|
-
const mod = await import(/* @vite-ignore */ ESBUILD_ESM_URL);
|
|
34
|
+
const mod: EsbuildWasm = await import(/* @vite-ignore */ ESBUILD_ESM_URL);
|
|
27
35
|
await mod.initialize({
|
|
28
36
|
wasmURL: ESBUILD_WASM_URL,
|
|
29
37
|
});
|
|
30
38
|
esbuildModule = mod;
|
|
39
|
+
return mod;
|
|
31
40
|
})();
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
await initPromise;
|
|
35
|
-
return esbuildModule;
|
|
43
|
+
return await initPromise;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
export function createEsbuild(): Record<string, unknown> {
|
|
@@ -22,6 +22,7 @@ import { createModuleShim } from './module.js';
|
|
|
22
22
|
import * as readlineModule from './readline.js';
|
|
23
23
|
import { createRimraf } from './rimraf.js';
|
|
24
24
|
import { createEsbuild } from './esbuild.js';
|
|
25
|
+
import { assertEqualHolds } from './loose-equality.js';
|
|
25
26
|
|
|
26
27
|
export interface NodeContext {
|
|
27
28
|
vfs: VFS;
|
|
@@ -114,9 +115,9 @@ export function createModuleMap(ctx: NodeContext): Record<string, () => unknown>
|
|
|
114
115
|
if (!value) throw new Error(message || 'AssertionError');
|
|
115
116
|
};
|
|
116
117
|
assert.ok = assert;
|
|
117
|
-
assert.equal = (a: unknown, b: unknown, msg?: string) => { if (a
|
|
118
|
+
assert.equal = (a: unknown, b: unknown, msg?: string) => { if (!assertEqualHolds(a, b)) throw new Error(msg || `${a} != ${b}`); };
|
|
118
119
|
assert.strictEqual = (a: unknown, b: unknown, msg?: string) => { if (a !== b) throw new Error(msg || `${a} !== ${b}`); };
|
|
119
|
-
assert.notEqual = (a: unknown, b: unknown, msg?: string) => { if (a
|
|
120
|
+
assert.notEqual = (a: unknown, b: unknown, msg?: string) => { if (assertEqualHolds(a, b)) throw new Error(msg || `${a} == ${b}`); };
|
|
120
121
|
assert.notStrictEqual = (a: unknown, b: unknown, msg?: string) => { if (a === b) throw new Error(msg || `${a} === ${b}`); };
|
|
121
122
|
assert.deepStrictEqual = (a: unknown, b: unknown, msg?: string) => {
|
|
122
123
|
if (JSON.stringify(a) !== JSON.stringify(b)) throw new Error(msg || 'deepStrictEqual failed');
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* loose-equality.ts — the `==` coercions, spelled out.
|
|
3
|
+
*
|
|
4
|
+
* `assert.equal`/`assert.notEqual` are specified against the `==` operator —
|
|
5
|
+
* Node's own docs call it "shallow, coercive equality between the actual and
|
|
6
|
+
* expected parameters using the == operator" — and since Node 14 both also
|
|
7
|
+
* treat two NaNs as identical, which `==` on its own does not. Guest code
|
|
8
|
+
* inside the sandbox sees those semantics, so the shim carries them here
|
|
9
|
+
* instead of the strict comparison a reader would expect of new code.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** The spec's Object type: everything callable or keyed, null excluded. */
|
|
13
|
+
function isObject(value: unknown): value is object {
|
|
14
|
+
return value !== null && (typeof value === 'object' || typeof value === 'function');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** ToPrimitive(value) with the default hint. */
|
|
18
|
+
function toPrimitive(value: object): unknown {
|
|
19
|
+
if (Symbol.toPrimitive in value) {
|
|
20
|
+
const exotic = value[Symbol.toPrimitive];
|
|
21
|
+
if (typeof exotic === 'function') return exotic.call(value, 'default');
|
|
22
|
+
}
|
|
23
|
+
for (const method of ['valueOf', 'toString'] as const) {
|
|
24
|
+
if (!(method in value)) continue;
|
|
25
|
+
const convert = value[method];
|
|
26
|
+
if (typeof convert !== 'function') continue;
|
|
27
|
+
const primitive: unknown = convert.call(value);
|
|
28
|
+
if (!isObject(primitive)) return primitive;
|
|
29
|
+
}
|
|
30
|
+
throw new TypeError('Cannot convert object to primitive value');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A bigint compares against the mathematical value of its counterpart, so a
|
|
35
|
+
* fractional number, a non-finite one, or a string naming no integer can
|
|
36
|
+
* never match one.
|
|
37
|
+
*/
|
|
38
|
+
function bigIntLooseEqual(big: bigint, other: unknown): boolean {
|
|
39
|
+
if (typeof other === 'number') return Number.isInteger(other) && BigInt(other) === big;
|
|
40
|
+
if (typeof other !== 'string') return false;
|
|
41
|
+
try {
|
|
42
|
+
return BigInt(other) === big;
|
|
43
|
+
} catch {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** IsLooselyEqual — what `a == b` answers. */
|
|
49
|
+
export function looseEqual(a: unknown, b: unknown): boolean {
|
|
50
|
+
if (a === null || a === undefined) return b === null || b === undefined;
|
|
51
|
+
if (b === null || b === undefined) return false;
|
|
52
|
+
|
|
53
|
+
// Two objects match only as the same reference; an object against a
|
|
54
|
+
// primitive matches on the object's own primitive value.
|
|
55
|
+
if (isObject(a)) {
|
|
56
|
+
return isObject(b) ? a === b : looseEqual(toPrimitive(a), b);
|
|
57
|
+
}
|
|
58
|
+
if (isObject(b)) return looseEqual(a, toPrimitive(b));
|
|
59
|
+
|
|
60
|
+
const aType = typeof a;
|
|
61
|
+
const bType = typeof b;
|
|
62
|
+
if (aType === bType) return a === b;
|
|
63
|
+
if (aType === 'boolean') return looseEqual(Number(a), b);
|
|
64
|
+
if (bType === 'boolean') return looseEqual(a, Number(b));
|
|
65
|
+
if (aType === 'symbol' || bType === 'symbol') return false;
|
|
66
|
+
if (typeof a === 'bigint') return bigIntLooseEqual(a, b);
|
|
67
|
+
if (typeof b === 'bigint') return bigIntLooseEqual(b, a);
|
|
68
|
+
return Number(a) === Number(b);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Whether Node's legacy assertions call these equal: `==`, widened by the
|
|
73
|
+
* both-sides-NaN case Node folded into `assert.equal` in v14. Node reaches
|
|
74
|
+
* for the non-coercing `Number.isNaN`, so a string spelling "NaN" is not it.
|
|
75
|
+
*/
|
|
76
|
+
export function assertEqualHolds(a: unknown, b: unknown): boolean {
|
|
77
|
+
if (typeof a === 'number' && typeof b === 'number') {
|
|
78
|
+
if (Number.isNaN(a) && Number.isNaN(b)) return true;
|
|
79
|
+
}
|
|
80
|
+
return looseEqual(a, b);
|
|
81
|
+
}
|
|
@@ -148,8 +148,11 @@ export function createProcess(opts: ProcessOptions) {
|
|
|
148
148
|
},
|
|
149
149
|
removeListener: (event: string, fn: (...args: unknown[]) => void) => proc.off(event, fn),
|
|
150
150
|
removeAllListeners: (event?: string) => {
|
|
151
|
-
if (event)
|
|
152
|
-
|
|
151
|
+
if (event) {
|
|
152
|
+
delete listeners[event];
|
|
153
|
+
} else {
|
|
154
|
+
for (const k of Object.keys(listeners)) delete listeners[k];
|
|
155
|
+
}
|
|
153
156
|
return proc;
|
|
154
157
|
},
|
|
155
158
|
listeners: (event: string) => listeners[event] ? [...listeners[event]] : [],
|