Complete node:os and add path.parse and path.format (#959)

This commit is contained in:
Chris Tate
2026-10-14 01:01:49 -05:00
committed by GitHub
37 changed files with 3531 additions and 192 deletions
+1 -1
View File
@@ -252,7 +252,7 @@ Use [Native FFI](/docs/ffi#replacing-a-node-api-addon) to call a plain C ABI fun
The `wasm32-wasi` executable target supports async/await, promises, generators, timers, portable event-loop work, stdin/readline, filesystem callbacks and promises.
WASI Preview 1 does not provide sockets, process spawning, OS signals, network interfaces, or filesystem notifications. Networking/fetch, child processes, signal APIs, `os.networkInterfaces()`, and `fs.watch` are rejected before linking with `SC3002`. `--sanitize` and native FFI are unavailable.
WASI Preview 1 does not provide sockets, process spawning, OS signals, network interfaces, or filesystem notifications. Networking/fetch, child processes, signal APIs, `os.networkInterfaces()`, the `os` host facts without a WASI equivalent (`cpus()`, `hostname()`, `freemem()`, `loadavg()`, `uptime()`, `getPriority()`), and `fs.watch` are rejected before linking with `SC3002`. `--sanitize` and native FFI are unavailable.
Library mode emits an async-free Wasm reactor with named host imports. Native runtime localization and thread instances are unsupported. Each Wasm instance owns independent state. Filesystem access is limited to directories opened by the host; `scriptc run` exposes the current working directory and `/tmp`. See [Platform Support](/docs/platforms) for build and run details.
+1 -1
View File
@@ -79,7 +79,7 @@ hello, world
Without `-o`, the module is written to `.scriptc/hello.wasm`. `scriptc run` runs it with Node.js's WASI implementation, so it needs Node.js 24 or later on your `PATH`. It passes through stdio and environment variables, makes the current directory available as `/`, and maps the host's temporary directory to `/tmp`. You can run a built module in any other WASI Preview 1 runtime too.
WASI modules support the same language features as native executables, including async functions, promises, generators, timers, stdin and readline, and filesystem callbacks and promises. WASI Preview 1 has no sockets, process spawning, signals, network interfaces, or file watching, so networking and `fetch`, child processes, signal APIs, `os.networkInterfaces()`, and `fs.watch` fail the build with `SC3002`. `--sanitize` and native FFI aren't available for WASI.
WASI modules support the same language features as native executables, including async functions, promises, generators, timers, stdin and readline, and filesystem callbacks and promises. WASI Preview 1 has no sockets, process spawning, signals, network interfaces, or file watching, so networking and `fetch`, child processes, signal APIs, `os.networkInterfaces()`, the `os` host facts without a WASI equivalent (`cpus()`, `hostname()`, `freemem()`, `loadavg()`, `uptime()`, `getPriority()`), and `fs.watch` fail the build with `SC3002`. `--sanitize` and native FFI aren't available for WASI.
To build a module that a web or native application calls into, see [WebAssembly Modules](/docs/wasm).
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.15.0",
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
"artifactVersion": "a83d6e1dfee84eea66b5",
"artifactVersion": "d94b8161bbee649b9890",
"rowCount": 3662
}
+46 -46
View File
@@ -494,9 +494,9 @@
"apiEntries": 24,
"static": {
"supported": 0,
"partial": 11,
"partial": 23,
"refused": 0,
"not-implemented": 13,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -509,9 +509,9 @@
"apiEntries": 17,
"static": {
"supported": 0,
"partial": 14,
"partial": 16,
"refused": 0,
"not-implemented": 3,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -49456,9 +49456,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49479,9 +49479,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49502,9 +49502,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49525,9 +49525,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49548,9 +49548,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49594,9 +49594,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49617,9 +49617,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49640,9 +49640,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49824,9 +49824,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49870,9 +49870,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49916,9 +49916,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -49939,9 +49939,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -50054,9 +50054,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -50169,9 +50169,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -8,8 +8,8 @@
"tiers": {
"static": {
"replace-refusal": 40,
"verify-gap": 2398,
"audit-partial": 768,
"verify-gap": 2384,
"audit-partial": 782,
"classify": 2,
"implement": 80
}
@@ -48814,12 +48814,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.arch",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48839,12 +48841,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.cpus",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48864,12 +48868,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.endianness",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48889,12 +48895,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.freemem",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48914,12 +48922,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.getPriority",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48967,12 +48977,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.hostname",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -48992,12 +49004,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.loadavg",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -49017,12 +49031,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.machine",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -49235,12 +49251,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.uptime",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -49288,12 +49306,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.version",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -49341,12 +49361,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.constants",
"tests": [
"tests/corpus/5141-os-constants.ts"
]
}
}
},
@@ -49366,12 +49388,14 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:os",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.os.devNull",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
}
}
},
@@ -49484,12 +49508,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:path",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.path.format",
"tests": [
"tests/corpus/5142-path-parse-format.ts",
"packages/runtime/test/path.test.ts"
]
}
}
},
@@ -49627,12 +49654,15 @@
"priority": "high",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:path",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.path.parse",
"tests": [
"tests/corpus/5142-path-parse-format.ts",
"packages/runtime/test/path.test.ts"
]
}
}
},
@@ -521,9 +521,9 @@
"apiEntries": 24,
"static": {
"supported": 0,
"partial": 11,
"partial": 23,
"refused": 0,
"not-implemented": 13,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -536,9 +536,9 @@
"apiEntries": 17,
"static": {
"supported": 0,
"partial": 14,
"partial": 16,
"refused": 0,
"not-implemented": 3,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -68631,7 +68631,9 @@
"tests": [
"tests/corpus/1480-os-network-interfaces.ts",
"tests/corpus/2917-builtin-callable-widths.ts",
"tests/corpus/4598-os-available-parallelism.ts"
"tests/corpus/4598-os-available-parallelism.ts",
"tests/corpus/5140-os-host-facts.ts",
"tests/corpus/5141-os-constants.ts"
]
},
"anchorSource": "chapter"
@@ -68864,8 +68866,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.arch",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -68887,8 +68892,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.cpus",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -68910,8 +68918,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.endianness",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -68933,8 +68944,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.freemem",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -68956,8 +68970,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.getPriority",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69006,8 +69023,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.hostname",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69029,8 +69049,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.loadavg",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69052,8 +69075,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.machine",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69260,8 +69286,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.uptime",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69310,8 +69339,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.version",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "exact"
},
@@ -69360,8 +69392,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.constants",
"tests": [
"tests/corpus/5141-os-constants.ts"
]
},
"anchorSource": "chapter"
},
@@ -69383,8 +69418,11 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:os"
"status": "partial",
"evidence": "surface-manifest:node-builtin.os.devNull",
"tests": [
"tests/corpus/5140-os-host-facts.ts"
]
},
"anchorSource": "chapter"
},
@@ -69409,10 +69447,12 @@
"status": "partial",
"evidence": "derived:descendants",
"tests": [
"packages/runtime/test/path.test.ts",
"tests/corpus/2915-path-function-values.ts",
"tests/corpus/2916-path-function-values-cjs.ts",
"tests/corpus/2917-builtin-callable-widths.ts",
"tests/corpus/2918-builtin-callable-widths-cjs.ts",
"tests/corpus/5142-path-parse-format.ts",
"tests/corpus/957-builtins-namespace.ts"
]
},
@@ -69549,8 +69589,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:path"
"status": "partial",
"evidence": "surface-manifest:node-builtin.path.format",
"tests": [
"tests/corpus/5142-path-parse-format.ts",
"packages/runtime/test/path.test.ts"
]
},
"anchorSource": "exact"
},
@@ -69685,8 +69729,12 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:path"
"status": "partial",
"evidence": "surface-manifest:node-builtin.path.parse",
"tests": [
"tests/corpus/5142-path-parse-format.ts",
"packages/runtime/test/path.test.ts"
]
},
"anchorSource": "exact"
},
@@ -81,7 +81,25 @@
"node-builtin.net": ["tests/corpus/2500-net-autosel-timeout.ts", "tests/corpus/2596-net-arg-ladders.cjs"],
"node-builtin.os": ["tests/corpus/1480-os-network-interfaces.ts", "tests/corpus/2917-builtin-callable-widths.ts"],
"node-builtin.os.availableParallelism": ["tests/corpus/4598-os-available-parallelism.ts"],
"node-builtin.os.arch": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.cpus": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.devNull": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.endianness": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.freemem": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.getPriority": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.hostname": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.loadavg": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.machine": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.uptime": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.version": ["tests/corpus/5140-os-host-facts.ts"],
"node-builtin.os.constants": ["tests/corpus/5141-os-constants.ts"],
"node-builtin.path": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2915-path-function-values.ts", "tests/corpus/2916-path-function-values-cjs.ts", "tests/corpus/2917-builtin-callable-widths.ts", "tests/corpus/2918-builtin-callable-widths-cjs.ts"],
"node-builtin.path.parse": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.path.format": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.path.posix.parse": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.path.posix.format": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.path.win32.parse": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.path.win32.format": ["tests/corpus/5142-path-parse-format.ts", "packages/runtime/test/path.test.ts"],
"node-builtin.perf_hooks": ["tests/corpus/957-builtins-namespace.ts"],
"node-builtin.process": ["tests/corpus/990-process-basics.ts", "tests/corpus/2314-process-introspection.ts"],
"node-builtin.process.hrtime": ["tests/corpus/process-hrtime.js"],
+253
View File
@@ -1600,6 +1600,24 @@ declare module "path" {
export function isAbsolute(path: string): boolean;
export function relative(from: string, to: string): string;
export function toNamespacedPath(path: string): string;
/* parse() splits a path into its root, directory, base, extension and
* name; format() joins those fields back (Node's `||` defaults). */
export interface ParsedPath {
root: string;
dir: string;
base: string;
ext: string;
name: string;
}
export interface FormatInputPathObject {
root?: string | undefined;
dir?: string | undefined;
base?: string | undefined;
ext?: string | undefined;
name?: string | undefined;
}
export function parse(path: string): ParsedPath;
export function format(pathObject: FormatInputPathObject): string;
/* The union @types/node declares: the bare module answers the TARGET
* platform's value (a compile-time constant — "\\"/";" under a win32
* triple, "/"/":" everywhere else). */
@@ -1625,6 +1643,8 @@ declare module "path/posix" {
export function isAbsolute(path: string): boolean;
export function relative(from: string, to: string): string;
export function toNamespacedPath(path: string): string;
export function parse(path: string): import("path").ParsedPath;
export function format(pathObject: import("path").FormatInputPathObject): string;
export const sep: "/";
export const delimiter: ":";
}
@@ -1641,6 +1661,8 @@ declare module "path/win32" {
export function isAbsolute(path: string): boolean;
export function relative(from: string, to: string): string;
export function toNamespacedPath(path: string): string;
export function parse(path: string): import("path").ParsedPath;
export function format(pathObject: import("path").FormatInputPathObject): string;
export const sep: "\\";
export const delimiter: ";";
}
@@ -1698,6 +1720,237 @@ declare module "os" {
}
export type NetworkInterfaceInfo = NetworkInterfaceInfoIPv4 | NetworkInterfaceInfoIPv6;
export function networkInterfaces(): { [name: string]: NetworkInterfaceInfo[] | undefined };
/* Host facts, libuv's answers: model/speed/times per logical CPU, the
* host name, the 1/5/15-minute load averages, uptime in seconds, free
* memory in bytes, process.arch, uname's machine and version fields,
* the byte order, and a process's scheduling priority (an omitted pid
* is the current process). Shapes mirror @types/node's. */
export interface CpuInfo {
model: string;
speed: number;
times: {
user: number;
nice: number;
sys: number;
idle: number;
irq: number;
};
}
export function cpus(): CpuInfo[];
export function hostname(): string;
export function loadavg(): number[];
export function uptime(): number;
export function freemem(): number;
export function arch(): string;
export function machine(): string;
export function version(): string;
export function endianness(): "BE" | "LE";
export function getPriority(pid?: number): number;
/* The TARGET platform's null device ("\\\\.\\nul" under a win32 triple). */
export const devNull: string;
/* The platform's constants: null-prototype objects with read-only
* members, `signals` frozen (Node's internalBinding('constants').os). */
export type SignalConstants = {
[
key in
| "SIGABRT"
| "SIGALRM"
| "SIGBUS"
| "SIGCHLD"
| "SIGCONT"
| "SIGFPE"
| "SIGHUP"
| "SIGILL"
| "SIGINT"
| "SIGIO"
| "SIGIOT"
| "SIGKILL"
| "SIGPIPE"
| "SIGPOLL"
| "SIGPROF"
| "SIGPWR"
| "SIGQUIT"
| "SIGSEGV"
| "SIGSTKFLT"
| "SIGSTOP"
| "SIGSYS"
| "SIGTERM"
| "SIGTRAP"
| "SIGTSTP"
| "SIGTTIN"
| "SIGTTOU"
| "SIGUNUSED"
| "SIGURG"
| "SIGUSR1"
| "SIGUSR2"
| "SIGVTALRM"
| "SIGWINCH"
| "SIGXCPU"
| "SIGXFSZ"
| "SIGBREAK"
| "SIGLOST"
| "SIGINFO"
]: number;
};
export namespace constants {
const UV_UDP_REUSEADDR: number;
namespace signals {}
const signals: SignalConstants;
namespace errno {
const E2BIG: number;
const EACCES: number;
const EADDRINUSE: number;
const EADDRNOTAVAIL: number;
const EAFNOSUPPORT: number;
const EAGAIN: number;
const EALREADY: number;
const EBADF: number;
const EBADMSG: number;
const EBUSY: number;
const ECANCELED: number;
const ECHILD: number;
const ECONNABORTED: number;
const ECONNREFUSED: number;
const ECONNRESET: number;
const EDEADLK: number;
const EDESTADDRREQ: number;
const EDOM: number;
const EDQUOT: number;
const EEXIST: number;
const EFAULT: number;
const EFBIG: number;
const EHOSTUNREACH: number;
const EIDRM: number;
const EILSEQ: number;
const EINPROGRESS: number;
const EINTR: number;
const EINVAL: number;
const EIO: number;
const EISCONN: number;
const EISDIR: number;
const ELOOP: number;
const EMFILE: number;
const EMLINK: number;
const EMSGSIZE: number;
const EMULTIHOP: number;
const ENAMETOOLONG: number;
const ENETDOWN: number;
const ENETRESET: number;
const ENETUNREACH: number;
const ENFILE: number;
const ENOBUFS: number;
const ENODATA: number;
const ENODEV: number;
const ENOENT: number;
const ENOEXEC: number;
const ENOLCK: number;
const ENOLINK: number;
const ENOMEM: number;
const ENOMSG: number;
const ENOPROTOOPT: number;
const ENOSPC: number;
const ENOSR: number;
const ENOSTR: number;
const ENOSYS: number;
const ENOTCONN: number;
const ENOTDIR: number;
const ENOTEMPTY: number;
const ENOTSOCK: number;
const ENOTSUP: number;
const ENOTTY: number;
const ENXIO: number;
const EOPNOTSUPP: number;
const EOVERFLOW: number;
const EPERM: number;
const EPIPE: number;
const EPROTO: number;
const EPROTONOSUPPORT: number;
const EPROTOTYPE: number;
const ERANGE: number;
const EROFS: number;
const ESPIPE: number;
const ESRCH: number;
const ESTALE: number;
const ETIME: number;
const ETIMEDOUT: number;
const ETXTBSY: number;
const EWOULDBLOCK: number;
const EXDEV: number;
const WSAEINTR: number;
const WSAEBADF: number;
const WSAEACCES: number;
const WSAEFAULT: number;
const WSAEINVAL: number;
const WSAEMFILE: number;
const WSAEWOULDBLOCK: number;
const WSAEINPROGRESS: number;
const WSAEALREADY: number;
const WSAENOTSOCK: number;
const WSAEDESTADDRREQ: number;
const WSAEMSGSIZE: number;
const WSAEPROTOTYPE: number;
const WSAENOPROTOOPT: number;
const WSAEPROTONOSUPPORT: number;
const WSAESOCKTNOSUPPORT: number;
const WSAEOPNOTSUPP: number;
const WSAEPFNOSUPPORT: number;
const WSAEAFNOSUPPORT: number;
const WSAEADDRINUSE: number;
const WSAEADDRNOTAVAIL: number;
const WSAENETDOWN: number;
const WSAENETUNREACH: number;
const WSAENETRESET: number;
const WSAECONNABORTED: number;
const WSAECONNRESET: number;
const WSAENOBUFS: number;
const WSAEISCONN: number;
const WSAENOTCONN: number;
const WSAESHUTDOWN: number;
const WSAETOOMANYREFS: number;
const WSAETIMEDOUT: number;
const WSAECONNREFUSED: number;
const WSAELOOP: number;
const WSAENAMETOOLONG: number;
const WSAEHOSTDOWN: number;
const WSAEHOSTUNREACH: number;
const WSAENOTEMPTY: number;
const WSAEPROCLIM: number;
const WSAEUSERS: number;
const WSAEDQUOT: number;
const WSAESTALE: number;
const WSAEREMOTE: number;
const WSASYSNOTREADY: number;
const WSAVERNOTSUPPORTED: number;
const WSANOTINITIALISED: number;
const WSAEDISCON: number;
const WSAENOMORE: number;
const WSAECANCELLED: number;
const WSAEINVALIDPROCTABLE: number;
const WSAEINVALIDPROVIDER: number;
const WSAEPROVIDERFAILEDINIT: number;
const WSASYSCALLFAILURE: number;
const WSASERVICE_NOT_FOUND: number;
const WSATYPE_NOT_FOUND: number;
const WSA_E_NO_MORE: number;
const WSA_E_CANCELLED: number;
const WSAEREFUSED: number;
}
namespace dlopen {
const RTLD_LAZY: number;
const RTLD_NOW: number;
const RTLD_GLOBAL: number;
const RTLD_LOCAL: number;
const RTLD_DEEPBIND: number;
}
namespace priority {
const PRIORITY_LOW: number;
const PRIORITY_BELOW_NORMAL: number;
const PRIORITY_NORMAL: number;
const PRIORITY_ABOVE_NORMAL: number;
const PRIORITY_HIGH: number;
const PRIORITY_HIGHEST: number;
}
}
}
declare module "node:os" {
export * from "os";
@@ -150,6 +150,10 @@ export const LIB_FN_SYMS: Record<string, string> = {
"path.win32Dirname": "scr_path_win32_dirname",
"path.win32Basename": "scr_path_win32_basename",
"path.win32Extname": "scr_path_win32_extname",
"path.parse": "scr_path_parse",
"path.win32Parse": "scr_path_win32_parse",
"path.format": "scr_path_format",
"path.win32Format": "scr_path_win32_format",
"path.win32IsAbsolute": "scr_path_win32_is_absolute",
"path.win32Relative": "scr_path_win32_relative",
"path.win32ToNamespacedPath": "scr_path_win32_to_namespaced_path",
@@ -157,6 +161,18 @@ export const LIB_FN_SYMS: Record<string, string> = {
"os.type": "scr_os_type",
"os.totalmem": "scr_os_totalmem",
"os.availableParallelism": "scr_os_available_parallelism",
"os.hostname": "scr_os_hostname",
"os.machine": "scr_os_machine",
"os.version": "scr_os_version",
"os.endianness": "scr_os_endianness",
"os.freemem": "scr_os_freemem",
"os.uptime": "scr_os_uptime",
"os.loadavg": "scr_os_loadavg",
"os.getPriority": "scr_os_get_priority",
"os.cpusTake": "scr_os_cpus_take",
"os.cpusModel": "scr_os_cpus_model",
"os.cpusField": "scr_os_cpus_field",
"os.constants": "scr_os_constants",
"os.release": "scr_os_release",
"os.userName": "scr_os_user_name",
"os.userShell": "scr_os_user_shell",
@@ -56,6 +56,7 @@ export const EXECUTABLE_RUNTIME_SOURCES = [
"scr_console.c",
"scr_lib.c",
"scr_path.c",
"scr_os.c",
"scr_url.c",
"scr_url_params.c",
"scr_json.c",
@@ -152,3 +152,17 @@ test("actual constructors retain host diagnostics while stored class values do n
fenceSpeculativeWasiFunctions(stored);
expect(moduleWasiUnavailableSurface(stored)).toBeNull();
});
test.each([
["os.cpusTake", "CPU enumeration"],
["os.hostname", "the host name"],
["os.freemem", "system memory statistics"],
["os.loadavg", "system load averages"],
["os.uptime", "system uptime"],
["os.getPriority", "process scheduling priority"],
] as const)("os host fact %s has no WASI lowering", (libFn, surface) => {
const read: IrExpr = { kind: "libCall", fn: libFn, args: [], type: VOID, loc };
const mod = module();
mod.functions = [fn("%main", [read])];
expect(moduleWasiUnavailableSurface(mod)?.surface).toContain(surface);
});
@@ -121,6 +121,16 @@ export function targetRefusalDiag(target: string, surface: string, loc: SrcLoc):
};
}
/** node:os host facts WASI Preview 1 has no interface for. */
const WASI_UNAVAILABLE_HOST_FACTS: ReadonlyMap<string, string> = new Map([
["os.cpusTake", "CPU enumeration (WASI Preview 1 has no processor API)"],
["os.hostname", "the host name (WASI Preview 1 has no host-name API)"],
["os.freemem", "system memory statistics (WASI Preview 1 has no memory API)"],
["os.loadavg", "system load averages (WASI Preview 1 has no load API)"],
["os.uptime", "system uptime (WASI Preview 1 has no boot-time API)"],
["os.getPriority", "process scheduling priority (WASI Preview 1 has no priority API)"],
]);
export function moduleWasiUnavailableSurface(
mod: IrModule,
): { surface: string; loc: SrcLoc } | null {
@@ -185,6 +195,11 @@ export function moduleWasiUnavailableSurface(
};
return false;
}
const hostFact = WASI_UNAVAILABLE_HOST_FACTS.get(node.fn);
if (hostFact !== undefined) {
found = { surface: hostFact, loc };
return false;
}
for (const [prefix, surface] of prefixes) {
if (node.fn.startsWith(prefix)) {
found = { surface, loc };
@@ -33,7 +33,14 @@ import { lowerProcessLoadEnvFile } from "./environment.js";
import { lowerFsWatchCall, lowerFsReaddirTypesCall } from "./filesystem.js";
import { lowerZlibModuleCall } from "./compression.js";
import { lowerForkCall } from "./child-process.js";
import { lowerOsNetworkInterfacesCall, lowerOsUserInfoCall } from "./operating-system.js";
import {
lowerOsCpusCall,
lowerOsGetPriorityCall,
lowerOsNetworkInterfacesCall,
lowerOsUserInfoCall,
lowerPathFormatCall,
lowerPathParseCall,
} from "./operating-system.js";
import { lowerQuerystringParseCall, lowerQuerystringStringifyCall } from "./querystring.js";
export function lowerBuiltinModuleCall(
@@ -128,6 +135,24 @@ export function lowerBuiltinModuleCall(
if (bi.module === "os" && bi.member === "userInfo") {
return lowerOsUserInfoCall(lowerer, expr, loc);
}
if (bi.module === "os" && bi.member === "cpus") {
return lowerOsCpusCall(lowerer, expr, loc);
}
if (bi.module === "os" && bi.member === "getPriority") {
return lowerOsGetPriorityCall(lowerer, expr, loc);
}
// path.parse/path.format: the bare module is the TARGET platform's
// family, the namespaces their own.
if (
(bi.module === "path" || bi.module === "path/posix" || bi.module === "path/win32") &&
(bi.member === "parse" || bi.member === "format")
) {
const win32 =
bi.module === "path/win32" || (bi.module === "path" && lowerer.targetPlatform === "win32");
return bi.member === "parse"
? lowerPathParseCall(lowerer, expr, win32, loc)
: lowerPathFormatCall(lowerer, expr, win32, loc);
}
// node:querystring — parse/stringify are entirely special-cased (the
// sep/eq/options completions, parse's call-site-shaped dictionary
// result, stringify's dyn-crossing object argument); decode/encode
@@ -1,6 +1,18 @@
import { numLit, strLit, varRef } from "../../../ir/build.js";
import * as ts from "../../ts7/adapter.js";
import { type Lowerer } from "../lowerer.js";
import { F64, type IrExpr, type IrType, STRING, type SrcLoc } from "../../../ir/ir.js";
import {
BOOL,
F64,
type IrExpr,
type IrStmt,
type IrType,
STRING,
type SrcLoc,
UNDEFINED_T,
arrayOf,
typeKey,
} from "../../../ir/ir.js";
/** `os.userInfo()` — the passwd-entry snapshot, Node's uv_os_get_passwd
* behind the call site's own mapped record shape: username = pw_name,
@@ -159,3 +171,407 @@ export function lowerOsNetworkInterfacesCall(
if (!saw4 || !saw6) fence();
return { kind: "libCall", fn: "os.networkInterfaces", args: [], type: result, loc };
}
/** `os.cpus()` — one uv_cpu_info snapshot as the call site's CpuInfo[]:
* os.cpusTake reads every CPU and answers the count, and a lifted helper
* builds one { model, speed, times: { user, nice, sys, idle, irq } }
* record per CPU from the snapshot accessors, in the mapped shapes' own
* field order. Other result shapes fence. */
export function lowerOsCpusCall(lowerer: Lowerer, call: ts.CallExpression, loc: SrcLoc): IrExpr {
if (call.arguments.length !== 0) {
lowerer.noLowering(
`cpus with ${call.arguments.length} arguments`,
call,
"cpus() takes no arguments",
);
}
const fence: () => never = () =>
lowerer.noLowering(
"cpus() where the result is not the CpuInfo array",
call,
"{ model, speed, times: { user, nice, sys, idle, irq } }[] is the supported result shape",
);
const result = lowerer.mapTypeOf(lowerer.typeOf(call));
if (result?.kind !== "array" || result.elem.kind !== "record") fence();
const info = result.elem;
const infoShape = lowerer.shapes.get(info.shapeId);
if (!infoShape || infoShape.tuple || infoShape.indexValue || infoShape.fields.length !== 3)
fence();
const timesField = infoShape.fields.find((f) => f.name === "times");
const timesT = timesField?.type;
if (timesT?.kind !== "record") fence();
const timesShape = lowerer.shapes.get(timesT.shapeId);
const TIMES = ["user", "nice", "sys", "idle", "irq"];
if (
!timesShape ||
timesShape.tuple ||
timesShape.indexValue ||
timesShape.fields.length !== TIMES.length ||
timesShape.fields.some((f) => !TIMES.includes(f.name) || f.type.kind !== "f64")
)
fence();
if (
infoShape.fields.find((f) => f.name === "model")?.type.kind !== "string" ||
infoShape.fields.find((f) => f.name === "speed")?.type.kind !== "f64"
)
fence();
const key = `os.cpus:${typeKey(result)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = `%os.cpus.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const index = varRef("i.0", F64, loc);
const field = (k: number): IrExpr => ({
kind: "libCall",
fn: "os.cpusField",
args: [index, numLit(k, loc)],
type: F64,
loc,
});
const times: IrExpr = {
kind: "recordLit",
fields: timesShape.fields.map((f) => ({
name: f.name,
value: field(1 + TIMES.indexOf(f.name)),
})),
type: timesT,
loc,
};
const record: IrExpr = {
kind: "recordLit",
fields: infoShape.fields.map((f) => ({
name: f.name,
value:
f.name === "model"
? { kind: "libCall", fn: "os.cpusModel", args: [index], type: STRING, loc }
: f.name === "speed"
? field(0)
: times,
})),
type: info,
loc,
};
const out = varRef("out.0", result, loc);
const count = varRef("n.0", F64, loc);
const body: IrStmt[] = [
{
kind: "varDecl",
localId: "n.0",
init: { kind: "libCall", fn: "os.cpusTake", args: [], type: F64, loc },
loc,
},
{
kind: "varDecl",
localId: "out.0",
init: { kind: "arrayLit", elems: [], type: result, loc },
loc,
},
{
kind: "for",
init: { kind: "varDecl", localId: "i.0", init: numLit(0, loc), loc },
cond: { kind: "bin", op: "<", left: index, right: count, type: BOOL, loc },
update: {
kind: "assign",
localId: "i.0",
value: { kind: "bin", op: "+", left: index, right: numLit(1, loc), type: F64, loc },
loc,
},
body: [
{
kind: "exprStmt",
expr: {
kind: "arrIntrinsic",
method: "push",
receiver: out,
args: [record],
type: F64,
loc,
},
loc,
},
],
loc,
},
{ kind: "return", value: out, loc },
];
lowerer.liftedFns.push({
name: helper,
params: [],
returnType: result,
locals: [
{ id: "n.0", name: "n", type: F64, mutable: false },
{ id: "out.0", name: "out", type: result, mutable: false },
{ id: "i.0", name: "i", type: F64, mutable: true },
],
body,
loc,
});
}
return { kind: "call", callee: helper, args: [], type: result, loc };
}
const PATH_FIELDS = ["root", "dir", "base", "ext", "name"];
/** `path.parse(p)` — the runtime's [root, dir, base, ext, name] parts as
* the call site's ParsedPath record (a lifted helper per shape). Other
* result shapes fence. */
export function lowerPathParseCall(
lowerer: Lowerer,
call: ts.CallExpression,
win32: boolean,
loc: SrcLoc,
): IrExpr {
const arg = call.arguments[0];
if (call.arguments.length !== 1 || !arg || ts.isSpreadElement(arg)) {
lowerer.noLowering("path.parse without exactly one path", call, "parse(path) takes one string");
}
const result = lowerer.mapTypeOf(lowerer.typeOf(call));
const shape = result?.kind === "record" ? lowerer.shapes.get(result.shapeId) : undefined;
if (
result?.kind !== "record" ||
!shape ||
shape.tuple ||
shape.indexValue ||
shape.fields.length !== PATH_FIELDS.length ||
shape.fields.some((f) => !PATH_FIELDS.includes(f.name) || f.type.kind !== "string")
) {
lowerer.noLowering(
"path.parse where the result is not the ParsedPath record",
call,
"{ root, dir, base, ext, name } is the supported result shape",
);
}
const fn = win32 ? "path.win32Parse" : "path.parse";
const key = `${fn}:${typeKey(result)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = `%path.parse.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const parts = varRef("parts.0", arrayOf(STRING), loc);
lowerer.liftedFns.push({
name: helper,
params: [{ localId: "path.0", name: "path", type: STRING }],
returnType: result,
locals: [
{ id: "path.0", name: "path", type: STRING, mutable: false },
{ id: "parts.0", name: "parts", type: arrayOf(STRING), mutable: false },
],
body: [
{
kind: "varDecl",
localId: "parts.0",
init: {
kind: "libCall",
fn,
args: [varRef("path.0", STRING, loc)],
type: arrayOf(STRING),
loc,
},
loc,
},
{
kind: "return",
value: {
kind: "recordLit",
fields: shape.fields.map((f) => ({
name: f.name,
value: {
kind: "arrayGet",
arr: parts,
index: numLit(PATH_FIELDS.indexOf(f.name), loc),
type: STRING,
loc,
},
})),
type: result,
loc,
},
loc,
},
],
loc,
});
}
return {
kind: "call",
callee: helper,
args: [lowerer.lowerExprExpecting(arg, STRING)],
type: result,
loc,
};
}
/** `path.format(pathObject)` — the object's root, dir, base, name and ext
* (each a string or an optional string; absent fields and undefined read
* as "", which Node's `||` treats alike) passed to the runtime's format.
* Other argument shapes fence. */
export function lowerPathFormatCall(
lowerer: Lowerer,
call: ts.CallExpression,
win32: boolean,
loc: SrcLoc,
): IrExpr {
const arg = call.arguments[0];
if (call.arguments.length !== 1 || !arg || ts.isSpreadElement(arg)) {
lowerer.noLowering(
"path.format without exactly one object",
call,
"format(pathObject) takes one object",
);
}
const fence: () => never = () =>
lowerer.noLowering(
"path.format with this argument type",
arg,
"an object whose root, dir, base, name and ext are strings or optional strings",
);
const value = lowerer.lowerExpr(arg);
if (value.type.kind !== "record") fence();
const recordT = value.type;
const shape = lowerer.shapes.get(recordT.shapeId);
if (!shape || shape.tuple || shape.indexValue) fence();
const fieldTypes = new Map<string, IrType>();
for (const f of shape.fields) {
if (!PATH_FIELDS.includes(f.name)) continue;
const ok =
f.type.kind === "string" ||
(f.type.kind === "union" &&
(lowerer.unions
.get(f.type.unionId)
?.arms.every((a) => a.kind === "string" || a.kind === "undefinedT") ??
false));
if (!ok) fence();
fieldTypes.set(f.name, f.type);
}
const fn = win32 ? "path.win32Format" : "path.format";
const key = `${fn}:${typeKey(recordT)}`;
let helper = lowerer.valueHelpers.get(key);
if (!helper) {
helper = `%path.format.${lowerer.valueHelpers.size}`;
lowerer.valueHelpers.set(key, helper);
const object = varRef("object.0", recordT, loc);
const read = (name: string): IrExpr => {
const type = fieldTypes.get(name);
if (!type) return strLit("", loc);
const get: IrExpr = {
kind: "recordGet",
obj: object,
shapeId: recordT.shapeId,
field: name,
type,
loc,
};
if (type.kind === "string") return get;
const unionId = (type as IrType & { kind: "union" }).unionId;
return {
kind: "ternary",
cond: {
kind: "unionIsTag",
unionId,
tag: lowerer.armTag(unionId, UNDEFINED_T),
negated: false,
value: get,
type: BOOL,
loc,
},
then: strLit("", loc),
else_: {
kind: "unionNarrow",
unionId,
tag: lowerer.armTag(unionId, STRING),
value: get,
type: STRING,
loc,
},
type: STRING,
loc,
};
};
lowerer.liftedFns.push({
name: helper,
params: [{ localId: "object.0", name: "object", type: recordT }],
returnType: STRING,
locals: [{ id: "object.0", name: "object", type: recordT, mutable: false }],
body: [
{
kind: "return",
value: {
kind: "libCall",
fn,
args: ["root", "dir", "base", "name", "ext"].map(read),
type: STRING,
loc,
},
loc,
},
],
loc,
});
}
return { kind: "call", callee: helper, args: [value], type: STRING, loc };
}
/** `os.getPriority(pid?)` — an omitted or undefined pid is 0 (Node's
* `if (pid === undefined) pid = 0`); the runtime validates the rest. */
export function lowerOsGetPriorityCall(
lowerer: Lowerer,
call: ts.CallExpression,
loc: SrcLoc,
): IrExpr {
const arg = call.arguments[0];
if (call.arguments.length > 1 || (arg && ts.isSpreadElement(arg))) {
lowerer.noLowering(
"getPriority with these arguments",
call,
"getPriority(pid?) takes one number",
);
}
let pid: IrExpr = numLit(0, loc);
if (arg) {
const value = lowerer.lowerExpr(arg);
if (value.type.kind === "f64") {
pid = value;
} else if (
value.type.kind === "union" &&
lowerer.armTag(value.type.unionId, UNDEFINED_T) >= 0 &&
lowerer.armTag(value.type.unionId, F64) >= 0 &&
lowerer.unions.get(value.type.unionId)?.arms.length === 2
) {
const unionId = value.type.unionId;
const temp = lowerer.declareHiddenLocal("%pid", value.type);
const ref = varRef(temp.id, value.type, loc);
pid = {
kind: "seqExpr",
stmts: [{ kind: "varDecl", localId: temp.id, init: value, loc }],
result: {
kind: "ternary",
cond: {
kind: "unionIsTag",
unionId,
tag: lowerer.armTag(unionId, UNDEFINED_T),
negated: false,
value: ref,
type: BOOL,
loc,
},
then: numLit(0, loc),
else_: {
kind: "unionNarrow",
unionId,
tag: lowerer.armTag(unionId, F64),
value: ref,
type: F64,
loc,
},
type: F64,
loc,
},
type: F64,
loc,
};
} else {
pid = lowerer.lowerExprExpecting(arg, F64);
}
}
return { kind: "libCall", fn: "os.getPriority", args: [pid], type: F64, loc };
}
@@ -155,6 +155,7 @@ import { mixinFnOfCallee } from "./lower-mixins.js";
import {
isConstAssertionTypeNode,
isGenericCallableMemberType,
isOsConstantsType,
isParseArgsDynTypeName,
underConstAssertion,
unitOnlyUnion,
@@ -1135,6 +1136,9 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
if (bi.module === "http" && bi.member === "STATUS_CODES") {
return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
}
if (bi.module === "os" && bi.member === "constants") {
return { kind: "libCall", fn: "os.constants", args: [], type: DYN, loc };
}
// tls.rootCertificates: a runtime-valued module constant (the
// cached bundled-CA array) — the one member read that lowers
// to a libCall instead of a baked literal.
@@ -2419,10 +2423,15 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
segmentDataSymbol?.name === "SegmentData" &&
lowerer.isStdlibSymbol(segmentDataSymbol) &&
["segment", "index", "input", "isWordLike"].includes(expr.name.text);
// os.constants members vary by platform (the declared groups list
// every platform's names): a number member reads as `number |
// undefined`, like an array element, and a group stays checked.
const osConstants = isOsConstantsType(lowerer.typeOf(expr.expression), lowerer);
if (
isParseArgsDynProperty(lowerer, expr) ||
isStatFsDynProperty(lowerer, expr) ||
segmentDataProperty
segmentDataProperty ||
osConstants
) {
const recv = lowerer.lowerExpr(expr.expression);
if (recv.type.kind === "dyn") {
@@ -2433,17 +2442,20 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
loc: locOf(expr.name),
};
const opt = hasOptionalChainGuard(expr.expression);
return lowerer.maybeNarrow(
{
kind: "dynKeyGet",
key,
...(opt ? { optional: true as const } : {}),
value: recv,
type: DYN,
loc,
},
expr,
);
const read: IrExpr = {
kind: "dynKeyGet",
key,
...(opt ? { optional: true as const } : {}),
value: recv,
type: DYN,
loc,
};
if (osConstants) {
return lowerer.mapTypeOf(lowerer.typeOf(expr))?.kind === "f64"
? lowerer.coerceToExpected(read, lowerer.withUndefinedArm(F64))
: read;
}
return lowerer.maybeNarrow(read, expr);
}
}
// A member read through a NULLISH generic binding (`const i: I<A &
@@ -207,6 +207,7 @@ import {
describeRecordMemberBlocker,
formatIrType,
FETCH_AMBIENT_TYPES,
isOsConstantsSymbol,
isUnitOnlyTsType,
isWorkerHandleType,
jsOpenObjectType,
@@ -3977,6 +3978,8 @@ export class Lowerer {
if (isIndexedRead(e)) return true;
if (ts.isPropertyAccessExpression(e) && isOptionalProcessStreamProperty(this, e)) return true;
if (ts.isPropertyAccessExpression(e) && isSettledIteratorValueRead(this, e)) return true;
if (ts.isPropertyAccessExpression(e) && isOsConstantsSymbol(symbolOf(e.expression), this))
return true;
if (ts.isCallExpression(e) && this.runtimeOptionalReduceTypes.has(e)) return true;
if (
ts.isCallExpression(e) &&
@@ -10938,6 +10941,10 @@ export class Lowerer {
if (bi.module === "http" && bi.member === "STATUS_CODES") {
return { kind: "libCall", fn: "http.statusCodes", args: [], type: DYN, loc };
}
// os.constants: the runtime's null-prototype constants object.
if (bi.module === "os" && bi.member === "constants") {
return { kind: "libCall", fn: "os.constants", args: [], type: DYN, loc };
}
// tls.rootCertificates through the namespace: the same runtime-valued
// constant the named-import read lowers to.
{
@@ -918,6 +918,11 @@ const PATH_MODULE_FNS: Record<string, BuiltinModuleFn | undefined> = {
result: STRING,
valueParams: [{ mode: "required", type: STRING }],
},
// Special-cased (lowerPathParseCall / lowerPathFormatCall): the
// ParsedPath record and the pathObject argument follow the call site's
// mapped shapes.
parse: { fn: "path.parse", params: [STRING], result: VOID },
format: { fn: "path.format", params: [], result: STRING },
};
/** The win32 twins (scr_path.c's Node-v24 path.win32 port, byte-for-byte):
@@ -987,6 +992,11 @@ const PATH_WIN32_MODULE_FNS: Record<string, BuiltinModuleFn | undefined> = {
result: STRING,
valueParams: [{ mode: "required", type: STRING }],
},
// Special-cased (lowerPathParseCall / lowerPathFormatCall): the
// ParsedPath record and the pathObject argument follow the call site's
// mapped shapes.
parse: { fn: "path.win32Parse", params: [STRING], result: VOID },
format: { fn: "path.win32Format", params: [], result: STRING },
};
export const BUILTIN_MODULE_FNS: Record<
@@ -1270,6 +1280,28 @@ export const BUILTIN_MODULE_FNS: Record<
result: F64,
valueParams: [],
},
// os.arch() IS process.arch, like platform().
arch: { fn: "process.arch", params: [], result: STRING, valueParams: [] },
hostname: { fn: "os.hostname", params: [], result: STRING, valueParams: [] },
// uv_os_uname's machine and version fields, read once at module load
// in Node (they never change while a process runs).
machine: { fn: "os.machine", params: [], result: STRING, valueParams: [] },
version: { fn: "os.version", params: [], result: STRING, valueParams: [] },
endianness: { fn: "os.endianness", params: [], result: STRING, valueParams: [] },
freemem: { fn: "os.freemem", params: [], result: F64, valueParams: [] },
uptime: { fn: "os.uptime", params: [], result: F64, valueParams: [] },
loadavg: { fn: "os.loadavg", params: [], result: arrayOf(F64), valueParams: [] },
// An omitted (or undefined) pid is 0, the current process
// (lowerOsGetPriorityCall completes it).
getPriority: {
fn: "os.getPriority",
params: [F64],
result: F64,
valueParams: [{ mode: "optional", type: F64, defaultValue: 0 }],
},
// Special-cased (lowerOsCpusCall): the call site's CpuInfo[] assembles
// from one uv_cpu_info snapshot.
cpus: { fn: "os.cpusTake", params: [], result: VOID },
// Entirely special-cased (lowerOsNetworkInterfacesCall): the result is
// the call site's mapped Dict<NetworkInterfaceInfo[]> shape, verified
// structurally there — this entry only routes the dispatch.
@@ -1613,7 +1645,7 @@ export const BUILTIN_MODULE_CONSTS: Record<
path: { sep: "/", delimiter: ":" },
"path/posix": { sep: "/", delimiter: ":" },
"path/win32": { sep: "\\", delimiter: ";" },
os: { EOL: "\n" },
os: { EOL: "\n", devNull: "/dev/null" },
worker_threads: { isMainThread: true, threadId: 0 },
cluster: { isPrimary: true, isMaster: true, isWorker: false },
};
@@ -1692,6 +1724,8 @@ export const BUILTIN_MODULE_FN_ALIASES: Record<
os: {
// lowerOsUserInfoCall assembles the record from scalar libCalls.
userInfo: ["os.userHomedir", "os.userShell"],
// lowerOsCpusCall reads the cpusTake snapshot through its accessors.
cpus: ["os.cpusModel", "os.cpusField"],
},
};
@@ -2106,6 +2140,15 @@ export const AMBIENT_SURFACE_FNS: readonly AmbientSurfaceRow[] = [
fns: ["tlsca.set"],
note: "replaces the default set and the client trust anchors for the rest of the process",
},
// os.constants is a VALUE read (lowerNamespaceBuiltinProperty and the
// named-import path): the platform's null-prototype constants object.
{
id: "node-builtin.os.constants",
kind: "node-builtin",
name: "os.constants",
fns: ["os.constants"],
note: "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined",
},
];
/** The win32-target overrides of the bare modules' constants: path's
@@ -2116,7 +2159,7 @@ const WIN32_TARGET_CONSTS: Record<
Record<string, string | number | boolean | undefined> | undefined
> = {
path: BUILTIN_MODULE_CONSTS["path/win32"],
os: { EOL: "\r\n" },
os: { EOL: "\r\n", devNull: "\\\\.\\nul" },
};
/** BUILTIN_MODULE_FNS with the platform-conditional modules bound per
@@ -1050,6 +1050,49 @@ let mapTypeDepth = 0;
* stay fenced instead of interning a per-context-wrong shape). */
let contextResolutions = 0;
/** os.constants and its groups — @types/node's `namespace constants`
* (errno, dlopen, priority) and SignalConstants, and the fallback's twins:
* the runtime's checked-dynamic constants object, whose members vary by
* platform. */
export function isOsConstantsType(
type: ts.Type,
ctx: Pick<TypeMapperCtx, "checker" | "isStdlibFile">,
): boolean {
return (
isOsConstantsSymbol(type.getAliasSymbol(), ctx, true) ||
isOsConstantsSymbol(type.getSymbol(), ctx, true)
);
}
/** A symbol declared as os.constants or one of its groups (a namespace, or
* with `types`, the SignalConstants alias; without it, the `signals`
* constant). The binding-symbol form needs no type query. */
export function isOsConstantsSymbol(
symbol: ts.Symbol | null | undefined,
ctx: Pick<TypeMapperCtx, "checker" | "isStdlibFile">,
types = false,
): boolean {
if (!symbol) return false;
for (const d of ctx.checker.declarationsOf(symbol)) {
const alias = types && ts.isTypeAliasDeclaration(d) && d.name.text === "SignalConstants";
const signals =
!types && ts.isVariableDeclaration(d) && ts.isIdentifier(d.name) && d.name.text === "signals";
if (!(alias || signals || ts.isModuleDeclaration(d)) || !ctx.isStdlibFile(d.getSourceFile()))
continue;
let inConstants = ts.isModuleDeclaration(d) && d.name.text === "constants";
for (let n: ts.Node | undefined = d.parent; n; n = n.parent) {
if (!ts.isModuleDeclaration(n)) continue;
if (ts.isStringLiteral(n.name)) {
if ((n.name.text === "os" || n.name.text === "node:os") && (alias || inConstants))
return true;
break;
}
if (n.name.text === "constants") inConstants = true;
}
}
return false;
}
/** worker_threads' Worker and MessagePort: owned runtime handles that keep
* their live object representation (DYN) wherever they are stored. */
function isWorkerHandleSymbol(
@@ -1164,6 +1207,7 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
) {
return { kind: "object", className: "%Error" };
}
if (isOsConstantsType(type, ctx)) return DYN;
// Immutable<T> (@scriptc/types) is a deep-readonly view: the value is
// still the T it maps.
const immutableSource = immutableSourceType(type, ctx);
@@ -2989,6 +3033,66 @@ function mapTypeInner(type: ts.Type, ctx: TypeMapperCtx): IrType | null {
// string arm. Key order is Node's runtime insertion order (node_os.cc
// builds uid, gid, username, homedir, shell), so Object.keys over the
// record answers exactly Node.
// os.CpuInfo (os.cpus()'s element), path.ParsedPath (path.parse()'s
// result) and path.FormatInputPathObject (path.format()'s argument):
// stdlib data interfaces interned explicitly, the AddressInfo pattern.
// Key order is Node's own (lib/os.js builds { model, speed, times:
// { user, nice, sys, idle, irq } }; path.parse builds { root, dir, base,
// ext, name }).
const cpuTimes = (): IrType => ({
kind: "record",
shapeId: ctx.shapes.intern(
["idle", "irq", "nice", "sys", "user"].map((name) => ({ name, type: F64 })),
false,
undefined,
["user", "nice", "sys", "idle", "irq"],
),
});
if (
psym &&
checker
.declarationsOf(psym)
.some(
(d) =>
ts.isTypeLiteralNode(d) &&
ts.isPropertySignature(d.parent) &&
ts.isIdentifier(d.parent.name) &&
d.parent.name.text === "times" &&
ts.isInterfaceDeclaration(d.parent.parent) &&
d.parent.parent.name.text === "CpuInfo" &&
ctx.isStdlibFile(d.getSourceFile()),
)
)
return cpuTimes();
if (isStdlibInterface("CpuInfo")) {
const times = cpuTimes();
return {
kind: "record",
shapeId: ctx.shapes.intern(
[
{ name: "model", type: STRING },
{ name: "speed", type: F64 },
{ name: "times", type: times },
],
false,
undefined,
["model", "speed", "times"],
),
};
}
if (isStdlibInterface("ParsedPath") || isStdlibInterface("FormatInputPathObject")) {
const field = psym!.name === "ParsedPath" ? STRING : withUndefinedArm(STRING, ctx.unions);
if (!field) return null;
return {
kind: "record",
shapeId: ctx.shapes.intern(
["base", "dir", "ext", "name", "root"].map((name) => ({ name, type: field })),
false,
undefined,
["root", "dir", "base", "ext", "name"],
),
};
}
if (isStdlibInterface("UserInfo")) {
const args = checker.getTypeArguments(widened as ts.TypeReference);
if (args.length > 1) return null;
@@ -460,6 +460,7 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"dyn.getOwnPropertyDescriptor",
"dyn.arrayProtoCall",
"process.chdir",
"os.getPriority",
"process.umask",
"process.loadEnvFile",
"fs.realpathSync",
@@ -399,6 +399,13 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"path.win32IsAbsolute": { argTypes: [STRING], result: BOOL },
"path.win32Relative": { argTypes: [STRING, STRING], result: STRING },
"path.win32ToNamespacedPath": { argTypes: [STRING], result: STRING },
/** path.parse: [root, dir, base, ext, name] as a fresh string[] (the
* frontend builds the ParsedPath record); path.format: the pathObject's
* root, dir, base, name and ext with undefined spelled "". */
"path.parse": { argTypes: [STRING], result: arrayOf(STRING) },
"path.win32Parse": { argTypes: [STRING], result: arrayOf(STRING) },
"path.format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING },
"path.win32Format": { argTypes: [STRING, STRING, STRING, STRING, STRING], result: STRING },
/** node:os: homedir is $HOME else getpwuid(3); tmpdir is Node's env
* cascade ($TMPDIR/$TMP/$TEMP else /tmp, one trailing slash trimmed).
* os.platform() lowers to process.platform — one implementation. */
@@ -414,6 +421,29 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"os.totalmem": { argTypes: [], result: F64 },
/** os.availableParallelism(): CPUs the process may use (libuv's answer), >= 1. Never throws. */
"os.availableParallelism": { argTypes: [], result: F64 },
/** os.hostname/machine/version/endianness, freemem, uptime and loadavg:
* libuv's answers (uname's machine and version fields, gethostname,
* "LE"/"BE", free memory in bytes, uptime in seconds, the three load
* averages). Fresh results; never throw. */
"os.hostname": { argTypes: [], result: STRING },
"os.machine": { argTypes: [], result: STRING },
"os.version": { argTypes: [], result: STRING },
"os.endianness": { argTypes: [], result: STRING },
"os.freemem": { argTypes: [], result: F64 },
"os.uptime": { argTypes: [], result: F64 },
"os.loadavg": { argTypes: [], result: arrayOf(F64) },
/** os.getPriority(pid): validateInt32 (RangeError ERR_OUT_OF_RANGE),
* then the priority; a failure throws Node's SystemError. */
"os.getPriority": { argTypes: [F64], result: F64 },
/** os.cpus(): cpusTake snapshots every CPU and answers the count;
* cpusModel(i) and cpusField(i, k) (k: 0 speed, 1 user, 2 nice, 3 sys,
* 4 idle, 5 irq) read the snapshot. Never throw. */
"os.cpusTake": { argTypes: [], result: F64 },
"os.cpusModel": { argTypes: [F64], result: STRING },
"os.cpusField": { argTypes: [F64, F64], result: F64 },
/** os.constants: the per-thread null-prototype constants object (dlopen,
* errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */
"os.constants": { argTypes: [], result: DYN },
/** umask(2): arg < 0 reads without setting (umask has no read-only form
* — set 0, restore); otherwise sets and answers the PREVIOUS mask.
* Never throws. */
+110
View File
@@ -1318,18 +1318,80 @@
"status": "static",
"note": "constant value read"
},
{
"id": "node-builtin.os.arch",
"kind": "node-builtin",
"name": "os.arch",
"status": "static"
},
{
"id": "node-builtin.os.availableParallelism",
"kind": "node-builtin",
"name": "os.availableParallelism",
"status": "static"
},
{
"id": "node-builtin.os.constants",
"kind": "node-builtin",
"name": "os.constants",
"status": "static",
"note": "the platform's dlopen, errno, signals and priority groups; members a platform lacks read as undefined"
},
{
"id": "node-builtin.os.cpus",
"kind": "node-builtin",
"name": "os.cpus",
"status": "static"
},
{
"id": "node-builtin.os.devNull",
"kind": "node-builtin",
"name": "os.devNull",
"status": "static",
"note": "constant value read"
},
{
"id": "node-builtin.os.endianness",
"kind": "node-builtin",
"name": "os.endianness",
"status": "static"
},
{
"id": "node-builtin.os.freemem",
"kind": "node-builtin",
"name": "os.freemem",
"status": "static"
},
{
"id": "node-builtin.os.getPriority",
"kind": "node-builtin",
"name": "os.getPriority",
"status": "static"
},
{
"id": "node-builtin.os.homedir",
"kind": "node-builtin",
"name": "os.homedir",
"status": "static"
},
{
"id": "node-builtin.os.hostname",
"kind": "node-builtin",
"name": "os.hostname",
"status": "static"
},
{
"id": "node-builtin.os.loadavg",
"kind": "node-builtin",
"name": "os.loadavg",
"status": "static"
},
{
"id": "node-builtin.os.machine",
"kind": "node-builtin",
"name": "os.machine",
"status": "static"
},
{
"id": "node-builtin.os.networkInterfaces",
"kind": "node-builtin",
@@ -1366,12 +1428,24 @@
"name": "os.type",
"status": "static"
},
{
"id": "node-builtin.os.uptime",
"kind": "node-builtin",
"name": "os.uptime",
"status": "static"
},
{
"id": "node-builtin.os.userInfo",
"kind": "node-builtin",
"name": "os.userInfo",
"status": "static"
},
{
"id": "node-builtin.os.version",
"kind": "node-builtin",
"name": "os.version",
"status": "static"
},
{
"id": "node-builtin.path",
"kind": "node-builtin",
@@ -1404,6 +1478,12 @@
"name": "path.extname",
"status": "static"
},
{
"id": "node-builtin.path.format",
"kind": "node-builtin",
"name": "path.format",
"status": "static"
},
{
"id": "node-builtin.path.isAbsolute",
"kind": "node-builtin",
@@ -1422,6 +1502,12 @@
"name": "path.normalize",
"status": "static"
},
{
"id": "node-builtin.path.parse",
"kind": "node-builtin",
"name": "path.parse",
"status": "static"
},
{
"id": "node-builtin.path.posix",
"kind": "node-builtin",
@@ -1454,6 +1540,12 @@
"name": "path/posix.extname",
"status": "static"
},
{
"id": "node-builtin.path.posix.format",
"kind": "node-builtin",
"name": "path/posix.format",
"status": "static"
},
{
"id": "node-builtin.path.posix.isAbsolute",
"kind": "node-builtin",
@@ -1472,6 +1564,12 @@
"name": "path/posix.normalize",
"status": "static"
},
{
"id": "node-builtin.path.posix.parse",
"kind": "node-builtin",
"name": "path/posix.parse",
"status": "static"
},
{
"id": "node-builtin.path.posix.relative",
"kind": "node-builtin",
@@ -1554,6 +1652,12 @@
"name": "path/win32.extname",
"status": "static"
},
{
"id": "node-builtin.path.win32.format",
"kind": "node-builtin",
"name": "path/win32.format",
"status": "static"
},
{
"id": "node-builtin.path.win32.isAbsolute",
"kind": "node-builtin",
@@ -1572,6 +1676,12 @@
"name": "path/win32.normalize",
"status": "static"
},
{
"id": "node-builtin.path.win32.parse",
"kind": "node-builtin",
"name": "path/win32.parse",
"status": "static"
},
{
"id": "node-builtin.path.win32.relative",
"kind": "node-builtin",
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5140-os-host-facts.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5141-os-constants.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/5142-path-parse-format.ts"
],
"diags": []
}
@@ -31,6 +31,7 @@ const BASE_RUNTIME_SOURCES = [
"scr_console.c",
"scr_lib.c",
"scr_path.c",
"scr_os.c",
"scr_url.c",
"scr_url_params.c",
"scr_json.c",
@@ -29,6 +29,7 @@ const BASE_RUNTIME_SOURCES = [
"scr_console.c",
"scr_lib.c",
"scr_path.c",
"scr_os.c",
"scr_url.c",
"scr_url_params.c",
"scr_json.c",
File diff suppressed because it is too large Load Diff
+203
View File
@@ -1279,3 +1279,206 @@ ScrStr *scr_path_win32_extname(ScrStr *path) {
}
return scr_str_new(p + start_dot, (size_t)(end - start_dot));
}
/* ── parse and format ─────────────────────────────────────────────────
* parse answers a fresh string[] [root, dir, base, ext, name] (the
* compiler builds the ParsedPath record from it); format takes the five
* fields with undefined spelled "" (Node reads each through `||`). */
static ScrArr *scr_path_parts(const char *p, long root[2], long dir[2], long base[2], long ext[2],
long name[2]) {
ScrArr *out = scr_arr_new(SCR_ELEM_STR, 5);
long *spans[5] = { root, dir, base, ext, name };
for (int k = 0; k < 5; k++)
scr_arr_push_ref(out, scr_str_new(p + spans[k][0], (size_t)(spans[k][1] - spans[k][0])));
return out;
}
ScrArr *scr_path_parse(ScrStr *path) {
const char *p = path->data;
long len = (long)path->len;
long root[2] = { 0, 0 }, dir[2] = { 0, 0 }, base[2] = { 0, 0 }, ext[2] = { 0, 0 },
name[2] = { 0, 0 };
if (len == 0) return scr_path_parts(p, root, dir, base, ext, name);
bool absolute = p[0] == '/';
long start = 0;
if (absolute) {
root[1] = 1;
start = 1;
}
long start_dot = -1, start_part = 0, end = -1;
bool matched_slash = true;
int pre_dot_state = 0;
for (long i = len - 1; i >= start; --i) {
char code = p[i];
if (code == '/') {
if (!matched_slash) {
start_part = i + 1;
break;
}
continue;
}
if (end == -1) {
matched_slash = false;
end = i + 1;
}
if (code == '.') {
if (start_dot == -1) start_dot = i;
else if (pre_dot_state != 1) pre_dot_state = 1;
} else if (start_dot != -1) {
pre_dot_state = -1;
}
}
if (end != -1) {
long from = start_part == 0 && absolute ? 1 : start_part;
if (start_dot == -1 || pre_dot_state == 0 ||
(pre_dot_state == 1 && start_dot == end - 1 && start_dot == start_part + 1)) {
base[0] = name[0] = from;
base[1] = name[1] = end;
} else {
name[0] = from;
name[1] = start_dot;
base[0] = from;
base[1] = end;
ext[0] = start_dot;
ext[1] = end;
}
}
if (start_part > 0) {
dir[1] = start_part - 1;
} else if (absolute) {
dir[1] = 1;
}
return scr_path_parts(p, root, dir, base, ext, name);
}
ScrArr *scr_path_win32_parse(ScrStr *path) {
const char *p = path->data;
long len = (long)path->len;
long root[2] = { 0, 0 }, dir[2] = { 0, 0 }, base[2] = { 0, 0 }, ext[2] = { 0, 0 },
name[2] = { 0, 0 };
if (len == 0) return scr_path_parts(p, root, dir, base, ext, name);
long root_end = 0;
char code = p[0];
if (len == 1) {
if (scr_path_w32_is_sep(code)) {
root[1] = dir[1] = 1;
} else {
base[1] = name[1] = 1;
}
return scr_path_parts(p, root, dir, base, ext, name);
}
if (scr_path_w32_is_sep(code)) {
/* A possible UNC root. */
root_end = 1;
if (scr_path_w32_is_sep(p[1])) {
long j = 2, last = j;
while (j < len && !scr_path_w32_is_sep(p[j])) j++;
if (j < len && j != last) {
last = j;
while (j < len && scr_path_w32_is_sep(p[j])) j++;
if (j < len && j != last) {
last = j;
while (j < len && !scr_path_w32_is_sep(p[j])) j++;
if (j == len) root_end = j;
else if (j != last) root_end = j + 1;
}
}
}
} else if (scr_path_w32_is_device_root(code) && p[1] == ':') {
/* A possible device root. */
if (len <= 2) {
root[1] = dir[1] = len;
return scr_path_parts(p, root, dir, base, ext, name);
}
root_end = 2;
if (scr_path_w32_is_sep(p[2])) {
if (len == 3) {
root[1] = dir[1] = len;
return scr_path_parts(p, root, dir, base, ext, name);
}
root_end = 3;
}
}
if (root_end > 0) root[1] = root_end;
long start_dot = -1, start_part = root_end, end = -1;
bool matched_slash = true;
int pre_dot_state = 0;
for (long i = len - 1; i >= root_end; --i) {
code = p[i];
if (scr_path_w32_is_sep(code)) {
if (!matched_slash) {
start_part = i + 1;
break;
}
continue;
}
if (end == -1) {
matched_slash = false;
end = i + 1;
}
if (code == '.') {
if (start_dot == -1) start_dot = i;
else if (pre_dot_state != 1) pre_dot_state = 1;
} else if (start_dot != -1) {
pre_dot_state = -1;
}
}
if (end != -1) {
if (start_dot == -1 || pre_dot_state == 0 ||
(pre_dot_state == 1 && start_dot == end - 1 && start_dot == start_part + 1)) {
base[0] = name[0] = start_part;
base[1] = name[1] = end;
} else {
name[0] = start_part;
name[1] = start_dot;
base[0] = start_part;
base[1] = end;
ext[0] = start_dot;
ext[1] = end;
}
}
/* A root directory keeps the whole root (`C:\abc` -> `C:\`); deeper
* directories drop the trailing separator. */
if (start_part > 0 && start_part != root_end) {
dir[1] = start_part - 1;
} else {
dir[0] = root[0];
dir[1] = root[1];
}
return scr_path_parts(p, root, dir, base, ext, name);
}
static ScrStr *scr_path_format_with(char sep, ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name,
ScrStr *ext) {
/* dir = pathObject.dir || pathObject.root; base = pathObject.base ||
* `${name || ''}${formatExt(ext)}`. */
ScrStr *d = dir->len > 0 ? dir : root;
PathBuf b;
pb_init(&b);
if (d->len > 0) {
pb_append(&b, d->data, d->len);
/* `${dir}${sep}${base}` unless dir is the root itself. */
bool is_root = d->len == root->len && memcmp(d->data, root->data, d->len) == 0;
if (!is_root) pb_push(&b, sep);
}
if (base->len > 0) {
pb_append(&b, base->data, base->len);
} else {
pb_append(&b, name->data, name->len);
if (ext->len > 0) {
if (ext->data[0] != '.') pb_push(&b, '.');
pb_append(&b, ext->data, ext->len);
}
}
return pb_take(&b);
}
ScrStr *scr_path_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name, ScrStr *ext) {
return scr_path_format_with('/', root, dir, base, name, ext);
}
ScrStr *scr_path_win32_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name,
ScrStr *ext) {
return scr_path_format_with('\\', root, dir, base, name, ext);
}
+28
View File
@@ -4198,6 +4198,14 @@ ScrStr *scr_path_win32_extname(ScrStr *path);
bool scr_path_win32_is_absolute(ScrStr *path);
ScrStr *scr_path_win32_relative(ScrStr *from, ScrStr *to);
ScrStr *scr_path_win32_to_namespaced_path(ScrStr *path);
/* path.parse / path.format for both families: parse answers a fresh
* string[] [root, dir, base, ext, name]; format takes the pathObject
* fields with undefined spelled "" (Node reads each through `||`). */
ScrArr *scr_path_parse(ScrStr *path);
ScrArr *scr_path_win32_parse(ScrStr *path);
ScrStr *scr_path_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name, ScrStr *ext);
ScrStr *scr_path_win32_format(ScrStr *root, ScrStr *dir, ScrStr *base, ScrStr *name,
ScrStr *ext);
/* fs.openSync(path, flags) → the raw fd as f64 (Node's string flag
* grammar; unknown flags throw Node's ERR_INVALID_ARG_VALUE TypeError
@@ -4439,6 +4447,26 @@ ScrStr *scr_os_tmpdir(void);
ScrStr *scr_os_release(void); /* uname(2) release — +1 fresh */
ScrStr *scr_os_type(void); /* uname(2) sysname — +1 fresh */
double scr_os_totalmem(void); /* total physical memory, bytes */
/* node:os host facts (scr_os.c), libuv's answers: uname machine/version,
* the host name, "LE"/"BE", free memory in bytes, uptime in seconds and
* the 1/5/15-minute load averages (+1 fresh results; never throw). */
ScrStr *scr_os_machine(void);
ScrStr *scr_os_version(void);
ScrStr *scr_os_hostname(void);
ScrStr *scr_os_endianness(void);
double scr_os_freemem(void);
double scr_os_uptime(void);
ScrArr *scr_os_loadavg(void);
/* os.getPriority(pid): validateInt32, then the scheduling priority;
* failures throw Node's RangeError or SystemError (ERR_SYSTEM_ERROR). */
double scr_os_get_priority(double pid);
/* os.cpus(): take answers the CPU count of a fresh snapshot; model and
* field (0 speed, 1 user, 2 nice, 3 sys, 4 idle, 5 irq) read it. */
double scr_os_cpus_take(void);
ScrStr *scr_os_cpus_model(double i);
double scr_os_cpus_field(double i, double field);
/* os.constants: the per-thread null-prototype constants object (+1). */
ScrDyn *scr_os_constants(void);
double scr_os_available_parallelism(void); /* uv_available_parallelism: CPUs this process may use, >= 1 */
/* os.userInfo()'s field trio (pw_name / pw_shell / pw_dir — the passwd
* home, not the $HOME cascade). +1 fresh; abort on lookup failure. */
+26
View File
@@ -218,6 +218,32 @@ for (const p of paths) {
emit("isAbsolute", [p], String(win32.isAbsolute(p)));
emit("toNamespacedPath", [p], win32.toNamespacedPath(p));
emit("resolve1", [p], win32.resolve(p));
// parse's five fields joined by U+0001, and format over them.
const parsed = win32.parse(p);
const fields = [parsed.root, parsed.dir, parsed.base, parsed.ext, parsed.name];
emit("parse", [p], fields.join("\u0001"));
emit(
"format",
[parsed.root, parsed.dir, parsed.base, parsed.name, parsed.ext],
win32.format(parsed),
);
}
// ── format's `||` defaults over field subsets ────────────────────────
for (const root of ["", "C:\\", "\\"]) {
for (const dir of ["", "C:\\", "d", "C:\\d"]) {
for (const base of ["", "b.x"]) {
for (const name of ["", "n"]) {
for (const ext of ["", "e", ".e"]) {
emit(
"format",
[root, dir, base, name, ext],
win32.format({ root, dir, base, name, ext }),
);
}
}
}
}
}
// ── basename with a suffix ───────────────────────────────────────────
File diff suppressed because it is too large Load Diff
+16
View File
@@ -157,6 +157,22 @@ int main(int argc, char **argv) {
scr_arr_release(pack);
} else if (strcmp(op, "relative") == 0) {
result = scr_path_win32_relative(args[0], args[1]);
} else if (strcmp(op, "parse") == 0) {
/* The five fields joined by U+0001, like the generator. */
ScrArr *parts = scr_path_win32_parse(args[0]);
char joined[MAX_FIELD];
size_t used = 0;
for (size_t k = 0; k < parts->len; k++) {
ScrStr *part = scr_arr_get_ref(parts, (double)k);
if (k > 0) joined[used++] = '\x01';
memcpy(joined + used, part->data, part->len);
used += part->len;
scr_str_release(part);
}
scr_arr_release(parts);
check(op, args, nargs, joined, used, expbuf, explen);
} else if (strcmp(op, "format") == 0) {
result = scr_path_win32_format(args[0], args[1], args[2], args[3], args[4]);
} else {
fprintf(stderr, "unknown op: %s\n", op);
return 2;
+57
View File
@@ -0,0 +1,57 @@
// node:os host facts: cpus, hostname, arch, machine, version, endianness,
// freemem, loadavg, uptime, devNull and getPriority. Host values differ
// between machines, so this prints their shapes, types and invariants.
import * as os from "node:os";
import { cpus, devNull, getPriority, hostname } from "node:os";
const list = os.cpus();
const first = list[0]!;
console.log("cpus", list.length > 0, list.length === cpus().length);
console.log(Object.keys(first), Object.keys(first.times), typeof first.model, typeof first.speed);
console.log(
list.every(
(cpu) =>
cpu.model.length >= 0 &&
Number.isInteger(cpu.speed) &&
cpu.speed >= 0 &&
[cpu.times.user, cpu.times.nice, cpu.times.sys, cpu.times.idle, cpu.times.irq].every(
(t) => Number.isInteger(t) && t >= 0,
),
),
);
console.log(JSON.stringify(Object.keys(JSON.parse(JSON.stringify(first)))));
console.log("hostname", typeof os.hostname(), os.hostname().length > 0, hostname() === os.hostname());
console.log("arch", os.arch() === process.arch, typeof os.machine(), os.machine().length > 0);
console.log("version", typeof os.version(), os.version() === os.version());
console.log("endianness", os.endianness());
const free = os.freemem();
console.log("freemem", typeof free, free > 0, free <= os.totalmem(), Number.isInteger(free));
const load = os.loadavg();
console.log("loadavg", Array.isArray(load), load.length, load.every((v) => typeof v === "number" && v >= 0));
const up = os.uptime();
console.log("uptime", typeof up, up > 0, os.uptime() >= up);
console.log("devNull", JSON.stringify(devNull), os.devNull === devNull);
const priority = os.getPriority();
console.log("priority", typeof priority, priority === getPriority(0), priority === os.getPriority(process.pid));
console.log(priority >= os.constants.priority.PRIORITY_HIGHEST && priority <= os.constants.priority.PRIORITY_LOW);
const maybe: number | undefined = undefined;
console.log(os.getPriority(maybe) === priority);
for (const pid of [1.5, -0.5, Number.NaN, 2 ** 31, -(2 ** 31) - 1, 2 ** 40]) {
try {
os.getPriority(pid);
console.log("no throw", pid);
} catch (e) {
const err = e as Error & { code?: string };
console.log(err.name, err.code, err.message);
}
}
try {
os.getPriority(2147483646);
} catch (e) {
const err = e as Error & { code?: string };
console.log(err instanceof Error, err.name, err.code, err.message);
}
+36
View File
@@ -0,0 +1,36 @@
// os.constants: the null-prototype groups, read-only numeric members,
// the frozen signals group, one object per program, and member reads
// that are missing on some platforms. Only values every platform shares
// are printed.
import * as os from "node:os";
import { constants } from "node:os";
console.log(Object.keys(os.constants));
console.log(os.constants === constants, os.constants.signals === constants.signals);
console.log(Object.getPrototypeOf(os.constants) === null, Object.getPrototypeOf(os.constants.errno) === null);
console.log(Object.isFrozen(os.constants.signals), Object.isFrozen(os.constants.errno));
console.log(os.constants.UV_UDP_REUSEADDR);
console.log(JSON.stringify(os.constants.priority));
console.log(os.constants.priority);
console.log(os.constants.signals.SIGINT, os.constants.signals.SIGTERM, os.constants.signals.SIGKILL);
console.log(os.constants.errno.ENOENT, os.constants.errno.EACCES, os.constants.errno.EEXIST);
// Dynamic names, entries and destructuring.
for (const name of ["SIGINT", "SIGTERM"] as const) console.log(name, os.constants.signals[name]);
const entries = Object.entries(os.constants.signals);
console.log(entries.length > 5, entries.some(([name, value]) => name === "SIGINT" && value === 2));
const { signals, errno, priority } = constants;
console.log(signals.SIGKILL, errno.ENOENT, priority.PRIORITY_NORMAL);
function lookup(name: string): number | undefined {
return (os.constants.signals as Record<string, number | undefined>)[name];
}
console.log(lookup("SIGTERM"), lookup("SIGNOPE"));
// A member some platforms lack reads as undefined until converted.
const breakSignal = os.constants.signals.SIGBREAK;
console.log(breakSignal === undefined || typeof breakSignal === "number");
// Numeric uses.
const term: number = os.constants.signals.SIGTERM;
console.log(term + 1, os.constants.signals.SIGINT * 2, Math.max(os.constants.priority.PRIORITY_LOW, 0));
console.log(`${os.constants.errno.ENOENT}`, String(os.constants.priority.PRIORITY_HIGH), os.constants.priority.PRIORITY_HIGHEST < 0);
+56
View File
@@ -0,0 +1,56 @@
// path.parse and path.format for the posix and win32 families: roots,
// trailing separators, dot files, extensions, UNC and drive roots, and
// format's `||` defaults, round-tripped through each other.
import * as path from "node:path";
import { format, parse } from "node:path/posix";
const posixCases = [
"", "/", "//", "///a//", ".", "..", "a/..", ".hidden", "/.hidden", "file.", "a.b.c",
"/a/b/c/", "./", "../x.y", "/home/user/dir/file.txt", "dir/sub/", "a//b", "/a/.b.c.",
"..ext", "/foo/bar.baz/", "x/y.z/..", "unicodé/fïle.tşt", "emoji/😀.png",
];
for (const p of posixCases) {
const parsed = path.posix.parse(p);
console.log(JSON.stringify(p), JSON.stringify(parsed), JSON.stringify(path.posix.format(parsed)));
}
const winCases = [
"", "C:", "C:\\", "C:\\a", "c:foo.txt", "\\", "/", "\\\\server\\share", "\\\\server\\share\\",
"\\\\server\\share\\a\\b.c", "\\\\server", "\\\\server\\", "a/b\\c.d", "\\\\?\\C:\\x.y",
"C:\\..\\x", "C:\\path\\dir\\file.txt", "C:/mixed/sep/f.e", "\\\\.\\COM1", "dir\\", ".\\.git",
"C:\\.hidden", "d:\\a\\..",
];
for (const p of winCases) {
const parsed = path.win32.parse(p);
console.log(JSON.stringify(p), JSON.stringify(parsed), JSON.stringify(path.win32.format(parsed)));
}
// format's defaults: dir wins over root, base over name + ext, and the
// separator is skipped when dir is the root.
const objects: path.FormatInputPathObject[] = [
{},
{ root: "/" },
{ dir: "/a" },
{ root: "/", dir: "/" , base: "x" },
{ root: "/", base: "x" },
{ dir: "a", base: "b" },
{ dir: "a", name: "n", ext: "e" },
{ dir: "a", name: "n", ext: ".e" },
{ name: "n" },
{ ext: "e" },
{ base: "", name: "n", ext: "" },
{ root: "/r/", dir: "/r/", name: "x", ext: ".y" },
{ root: "/x", dir: "", base: "b" },
{ dir: undefined, root: undefined, name: "only" },
];
objects.forEach((o, i) => {
console.log(i, JSON.stringify(path.posix.format(o)), JSON.stringify(path.win32.format(o)));
});
// Named imports, the bare module and typed bindings.
const parsed: path.ParsedPath = parse("/srv/app/index.js");
console.log(parsed.root, parsed.dir, parsed.base, parsed.ext, parsed.name, format(parsed));
const keys = Object.keys(path.parse("x/y.z"));
console.log(keys.join(","), path.format({ dir: "x", base: "y.z" }) === path.join("x", "y.z"));
const back = path.win32.format(path.win32.parse("C:\\Users\\me\\notes.md"));
console.log(back, path.win32.parse(back).name);
+2 -2
View File
@@ -34,10 +34,10 @@ ReadableStream.from(new Set([1, 2]));
* typecheck under @types/node and fence with the module-qualified name —
* calls and value reads alike. */
import { watchFile } from "fs";
import { cpus } from "node:os";
import { setPriority } from "node:os";
import { win32 } from "path";
watchFile("x", () => {});
console.log(cpus().length);
setPriority(0);
console.log(win32.sep);
/* URL getters and serialization lower under @types/node, but object-URL
* registry operations still fence by member name with the supported
@@ -57,22 +57,22 @@ fenced.ts:39:1 - error SC2020: 'fs.watchFile' is typed by @types/node but has no
38 | import { win32 } from "path";
39 | watchFile("x", () => {});
| ^~~~~~~~~~~~~~~~~~~~~~~~
40 | console.log(cpus().length);
40 | setPriority(0);
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
fenced.ts:40:13 - error SC2020: 'CpuInfo[].length' is part of the standard library types but has no scriptc lowering yet
fenced.ts:40:1 - error SC2020: 'os.setPriority' is typed by @types/node but has no scriptc lowering yet
39 | watchFile("x", () => {});
40 | console.log(cpus().length);
| ^~~~~~~~~~~~~
40 | setPriority(0);
| ^~~~~~~~~~~~~~
41 | console.log(win32.sep);
hint: the type checker sees the full standard library, but only the supported surface compiles (https://scriptc.dev/limitations)
hint: the type checker sees everything @types/node declares, but only the supported surface compiles (https://scriptc.dev/limitations)
fenced.ts:41:13 - error SC2020: 'PlatformPath.sep' is typed by @types/node but has no scriptc lowering yet
40 | console.log(cpus().length);
40 | setPriority(0);
41 | console.log(win32.sep);
| ^~~~~~~~~
42 | /* URL getters and serialization lower under @types/node, but object-URL