File size: 5,623 Bytes
488d96b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | /**
* The `process` global the worker needs before any VFS module runs. Cordis
* reads `process.env` and `process.versions.node` while the Loader is
* constructed, and `cordis.yml` keeps its `!!js process.*` expressions, so the
* configuration bytes stay identical to the Node deployment. Third-party Node
* packages use the presence of `process.title` to avoid browser-only globals.
* @module @deepseek-ai/dsh-experimental-webworker-runtime/src/node/globals/process
*/
import { requireActiveModuleLoader } from '../../module-system/module-loader.ts'
import { processAlive, signalProcess } from '../process-table.ts'
/** Construction inputs for {@link installProcessGlobal}. */
export interface ProcessShimOptions {
/** Virtual root reported by `cwd()`. */
readonly cwd: string
/** Environment the tree reads; `DSH_HOME` belongs here. */
readonly env: Readonly<Record<string, string>>
/** Argument vector reported to the tree. */
readonly argv?: readonly string[]
}
/** The members this shim publishes. */
export interface ProcessShim {
readonly env: Record<string, string>
readonly argv: string[]
readonly execArgv: string[]
/** Virtual host identity; spawning this path reports ENOENT because Node execution is unavailable. */
readonly execPath: string
/** Node process identity used by dependencies for environment detection. */
readonly title: string
/**
* Node 22 `process.getBuiltinModule`: the worker's module proxy for a
* builtin id (`fs`, `node:fs`), or undefined for anything else — it never
* resolves image modules.
* @param id - Builtin module id, with or without the `node:` prefix.
* @returns the proxied builtin, or undefined.
*/
getBuiltinModule(id: string): unknown
readonly platform: string
readonly arch: string
readonly pid: number
readonly version: string
readonly versions: Record<string, string>
cwd(): string
/**
* Signal one command started through the `node:child_process` shim. Signal
* `0` is the liveness probe the subprocess service polls a process tree
* with; a negative pid addresses the group, which here holds exactly the one
* command that leads it.
* @param pid - the target pid, negative for its group.
* @param signal - signal name, or `0` to probe without delivering one.
* @returns true once the signal is recorded.
* @throws Error with `code: 'ESRCH'` when no such command is running.
*/
kill(pid: number, signal?: NodeJS.Signals | 0): boolean
nextTick(callback: (...args: unknown[]) => void, ...args: unknown[]): void
readonly stdout: { write(chunk: string): boolean }
readonly stderr: { write(chunk: string): boolean }
on(): ProcessShim
off(): ProcessShim
once(): ProcessShim
prependListener(): ProcessShim
prependOnceListener(): ProcessShim
removeListener(): ProcessShim
removeAllListeners(): ProcessShim
listeners(): unknown[]
listenerCount(): number
setMaxListeners(): ProcessShim
emit(): boolean
readonly hrtime: { bigint(): bigint }
uptime(): number
exit(code?: number): void
}
/**
* Publish `globalThis.process`.
*
* `versions.node` is `0.0.0` on purpose: it makes Cordis's
* `ModuleLoader.fromInternal()` return undefined instead of reaching for Node
* internals, which is what lets the worker install its own module seam.
* @param options - Root, environment, and argument vector.
* @returns The published object, for the module proxy table.
*/
export function installProcessGlobal(options: ProcessShimOptions): ProcessShim {
const start = performance.now()
const write = (target: 'log' | 'error') => (chunk: string): boolean => {
console[target](chunk.replace(/\n$/, ''))
return true
}
const shim: ProcessShim = {
env: { ...options.env },
argv: [...(options.argv ?? ['node', 'dsh-webworker'])],
execArgv: [],
execPath: '/dsh/bin/node',
title: 'dsh-webworker',
platform: 'linux',
arch: 'x64',
pid: 1,
version: 'v0.0.0',
versions: { node: '0.0.0' },
cwd: () => options.cwd,
getBuiltinModule: (id: string): unknown => {
let resolution
try {
resolution = requireActiveModuleLoader().resolve(id, '/')
} catch {
// No loader mounted yet, or an id that resolves nowhere: Node answers
// undefined for non-builtins instead of throwing.
return undefined
}
return resolution.kind === 'static' ? resolution.factory() : undefined
},
kill: (pid: number, signal: NodeJS.Signals | 0 = 'SIGTERM'): boolean => {
if (signal === 0) {
if (processAlive(pid)) return true
const error = new Error('kill ESRCH') as NodeJS.ErrnoException
error.code = 'ESRCH'
error.syscall = 'kill'
throw error
}
return signalProcess(pid, signal)
},
nextTick: (callback, ...args) => { queueMicrotask(() => { callback(...args) }) },
stdout: { write: write('log') },
stderr: { write: write('error') },
on: () => shim,
off: () => shim,
once: () => shim,
prependListener: () => shim,
prependOnceListener: () => shim,
removeListener: () => shim,
removeAllListeners: () => shim,
listeners: () => [],
listenerCount: () => 0,
setMaxListeners: () => shim,
emit: () => false,
hrtime: { bigint: () => BigInt(Math.round((performance.now() - start) * 1e6)) },
uptime: () => (performance.now() - start) / 1000,
exit: (code?: number) => { console.warn(`webworker process: exit(${String(code ?? 0)}) requested; the worker keeps running`) },
}
;(globalThis as { process?: unknown }).process = shim
return shim
}
|