# nodeJs.runtime — 轻量 Node.js 运行时

用于直接运行 Node.js CLI、npm-cli.js 或独立 JavaScript 脚本，不启动旧 `nodeJs` RPC 环境。

- 导入不创建进程、不下载，不分配端口，不修改父进程环境或第三方模块。
- `resolve(options)` 只定位、运行校验现有 Node；`ensure(options)` 必要时准备固定版本便携 Node。
- 返回纯描述符：`nodeExePath/npmCliJsPath/nodeDir/version/arch/source`，可跨线程传值。
- `popen(runtime,cmdArgs,startInfo)` 返回原生 `process.popen`，UTF-8；不隐式安装。

## 快速上手

```aardio
import nodeJs.runtime;

var runtime,err,code = nodeJs.runtime.ensure();
if(!runtime) return null,err,code;

var pipe,err = nodeJs.runtime.popen(runtime,["-p","process.versions.node"]);
if(!pipe) return null,err;
var text,stderr,exitCode = pipe.readAll(); // readAll 默认负责 close
return {text=text;stderr=stderr;exitCode=exitCode};
```

`resolve/ensure` 校验会短暂运行候选 `node.exe`，不是纯 PE 元数据读取，也不是样本沙箱；不要把不可信程序冒充运行时传入。

## 定位与版本策略

默认依次检查 `NODE_EXE`、PATH、Windows 标准 Node 安装目录、受管便携目录。候选必须能运行，实际架构匹配，默认要求同目录下有 `node_modules/npm/bin/npm-cli.js`。

| 选项 | 含义 |
|---|---|
| `nodeExePath` | 显式路径；仅校验此候选，失败不回退或替换 |
| `useSystem=false` | 禁用环境变量、PATH 和系统目录复用，只用受管便携运行时 |
| `version` | 便携版的固定 `x.y.z`，默认 `24.22.2`；不接受 latest |
| `arch` | 实际 Node 进程架构，默认 x64（32 位 Windows 为 ia32），也可指定 arm64；x86 作为 ia32 别名 |
| `requireNpm=false` | 接受独立 node.exe，描述符的 npmCliJsPath 可能为 null |
| `accept(runtime)` | 调用者的版本兼容判断；返回假值拒绝此候选 |
| `installRoot` | 默认 `io.appData("aardio/std/nodeJs/runtime")` |
| `probeTimeout` | 每个候选运行校验的超时，默认 5000ms，上限 60000ms |
| `stop()` | 阶段间/接收过程中检查取消，返回真值取消 |

**`version` 只固定便携下载版本，不代表系统候选必须等于该版本。** 使用者根据实际工具要求提供 `accept`；本库不实现 npm semver 求解器，也不隐式升级系统 Node。

例如，只接受指定版本：

```aardio
import nodeJs.runtime;
var runtime,err,code = nodeJs.runtime.ensure({
    version="24.22.2";
    accept=lambda(rt) rt.version=="24.22.2";
});
return runtime,err,code;
```

## 便携准备与完整性

`ensure` 先复用兼容候选，确有缺口才执行：

1. 取得按目标路径隔离的安装锁并重新检查。
2. 从 Node 官方 HTTPS `SHASUMS256.txt` 取得对应 ZIP 的 SHA256；也可传入已从可信来源冻结的 `sha256`。
3. 归档下载到 `installRoot/.cache/`，校验后才解压到短暂存目录。
4. 预检 Windows 路径长度，确认每个归档文件已解出，实际运行验证版本/架构及 npm 入口。
5. 写入 `.runtime.json` 校验记录，再提交完整目录；失败不标 ready，不替换已有兼容目录。

可选 `downloadUrl` 只改变 ZIP 传输地址，**必须同时指定可信 SHA256**。`onProgress(received,total)` 接收累计字节数。取消会清除本次不完整归档和暂存目录；已校验归档保留，重复准备无需重新下载。坏 hash 返回 `integrity`，保留归档供诊断；确认原因后可删除对应 `.cache/*.zip` 再重试。

下载/解压/安装锁是同步操作，建议在工作线程执行。超时选项不是整个安装的统一 deadline。路径过长会明确失败，应改用更短的 `installRoot`，不会把部分 npm 当成成功。未完成校验提交的受管目录不会被 `resolve` 接受。

默认不要求管理员权限，不写系统 PATH、NODE_PATH、npm prefix 或 `.npmrc`，不修改其它 Node 安装。

## 启动参数、环境和 npm

`cmdArgs/startInfo` 遵循 `process.popen`：单字符串是命令行文本，数组/表是字面参数；不预加引号、不修改调用者数组。子环境和工作目录通过 STARTUPINFO 传递，不改父环境。

```aardio
import nodeJs.runtime;
var rt,err = nodeJs.runtime.resolve();
if(!rt) return null,err;

// 不经过 npm.cmd/cmd.exe，不初始化 Node RPC。
var p,err = nodeJs.runtime.popen(rt,[rt.npmCliJsPath,"--version"],{
    workDir=io.fullpath("/");
    environment={NPM_CONFIG_PREFIX=io.appData("myApp/npm")};
});
if(!p) return null,err;
return p.readAll();
```

`readAll` 首返混合 stdout/stderr；JSON 协议或长任务需要分别、有界排空双管道。不要先把 stdout 读到 EOF 再读 stderr。运行校验清空其子进程的 NODE_OPTIONS/NODE_PATH；普通 `popen` 尊重调用者的子环境设置。

## 需要整树监管时

本库不为原生管道再包装一层生命周期对象。直接组合独立 Job：

```aardio
import nodeJs.runtime;
import process.job.limitKill;
var rt,err = nodeJs.runtime.ensure();
if(!rt) return null,err;
var job,err = process.job.limitKill();
if(!job) return null,err;
var pipe,err,code,pending = job.popen(rt.nodeExePath,["-p","42"]);
if(!pipe){job.dispose();return null,err,code,pending;}
pipe.codepage=65001;
var text,stderr,exitCode = pipe.readAll();
var ok,err,code = job.dispose(2000);
if(!ok) return null,err,code,job;
return {text=text;stderr=stderr;exitCode=exitCode};
```

必须持有 `job`，完成后主动 `dispose`；只关闭 `pipe` 不保证后代进程退出。详见 Job 生命周期文档。
