From a9f4ff752d4992c3707bc4ba5e4472bcbaf99752 Mon Sep 17 00:00:00 2001 From: wangdequan Date: Sun, 2 Aug 2026 08:47:06 -0400 Subject: [PATCH] P0: pin project runtime and document FreeCAD parity plan --- .gitignore | 2 +- .node-version | 1 + .npmrc | 3 + .nvmrc | 1 + README.md | 8 +- config/freecad-baseline.json | 6 +- config/node-runtime.env | 8 + config/runtime-baseline.json | 1 + docs/freecad-full-parity-plan.zh-CN.md | 305 ++++++++++++++++++++++ docs/project-runtime.zh-CN.md | 90 +++++++ docs/web-cad-implementation-plan.zh-CN.md | 6 + nodew | 6 + npmw | 7 + package-lock.json | 2 +- package.json | 6 +- scripts/bootstrap-node.sh | 90 +++++++ scripts/check-runtime.mjs | 9 + 17 files changed, 540 insertions(+), 11 deletions(-) create mode 100644 .node-version create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 config/node-runtime.env create mode 100644 docs/freecad-full-parity-plan.zh-CN.md create mode 100644 docs/project-runtime.zh-CN.md create mode 100755 nodew create mode 100755 npmw create mode 100755 scripts/bootstrap-node.sh create mode 100644 scripts/check-runtime.mjs diff --git a/.gitignore b/.gitignore index 74bb372..07113cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,9 @@ node_modules/ dist/ .vite/ +.runtime/ *.log .env .env.* !.env.example .DS_Store - diff --git a/.node-version b/.node-version new file mode 100644 index 0000000..c947119 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +22.23.2 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9524854 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +engine-strict=true +fund=false +audit=false diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..5f70e59 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v22.23.2 diff --git a/README.md b/README.md index b0f1cab..76c19e3 100644 --- a/README.md +++ b/README.md @@ -15,16 +15,16 @@ The current milestone is a facade-first integration slice. Part Design document ## Development ```bash -npm install -npm run dev +./npmw install +./npmw run dev ``` -Node.js 22 or newer is the supported development baseline, matching the SQLite WASM package engine requirement. +The repository uses a project-local, checksum-verified Node.js `22.23.2` runtime and npm `10.9.8`. The wrapper downloads it to the ignored `.runtime/` directory and never changes `/usr/bin/node`; this removes the engine warning caused by running npm with an older system Node. `./nodew` runs the same pinned Node directly. See [docs/project-runtime.zh-CN.md](docs/project-runtime.zh-CN.md) for recovery and CI rules. Build verification: ```bash -npm run build +./npmw run verify ``` The SQLite OPFS worker requires cross-origin isolation headers. Vite dev and preview apply `Cross-Origin-Opener-Policy: same-origin` and `Cross-Origin-Embedder-Policy: require-corp`; deployments must preserve them. diff --git a/config/freecad-baseline.json b/config/freecad-baseline.json index 871c8fc..78598f9 100644 --- a/config/freecad-baseline.json +++ b/config/freecad-baseline.json @@ -1,10 +1,10 @@ { "baselineId": "freecad-1.1.1", "freecadVersion": "1.1.1", - "sourceRef": "FreeCAD-1.1.1", + "sourceRef": "FreeCAD-1.1.1@0108fd4b4850cc46e625b60e53cea7a7bbe69f8d", "sourceUrl": "https://github.com/FreeCAD/FreeCAD/releases/tag/1.1.1", "uiContract": "FreeCAD official desktop UI and Developers Handbook", "compatibilityLevels": ["exact", "compatible", "read-only", "proxy", "unsupported"], - "status": "baseline-pending-source-verification", - "notes": "The exact source commit and build flags must be recorded by P0-01 before geometry implementation." + "status": "release-tag-and-source-commit-verified", + "notes": "The official 1.1.1 release tag resolves to the recorded commit. Build flags, compiler image, and enabled workbenches remain P0-01 deliverables." } diff --git a/config/node-runtime.env b/config/node-runtime.env new file mode 100644 index 0000000..fdf3b85 --- /dev/null +++ b/config/node-runtime.env @@ -0,0 +1,8 @@ +# Project-local JavaScript toolchain. Keep this file free of shell expansions. +NODE_VERSION=22.23.2 +NODE_NPM_VERSION=10.9.8 +NODE_DIST_BASE=https://nodejs.org/dist/v22.23.2 +NODE_LINUX_X64_SHA256=d60acfe00a2932254bb0ad20e01b0d74397a0875595de719654b214f4b03f307 +NODE_LINUX_ARM64_SHA256=fff4078c5def658577f92c88db7db3bc0072924bfb93fe52c1e744a54e94abb8 +NODE_DARWIN_X64_SHA256=58e99022c2ff89395576cc7fd4d98cea24bb68081475d5f88b801ee8729fb026 +NODE_DARWIN_ARM64_SHA256=61130f394c1630d211dd50aecc4353d379480f36d3ac913cd85dbba1aed585c6 diff --git a/config/runtime-baseline.json b/config/runtime-baseline.json index 2336aa2..d69188b 100644 --- a/config/runtime-baseline.json +++ b/config/runtime-baseline.json @@ -1,6 +1,7 @@ { "baselineId": "bitbybit-web-cad-runtime-0.1", "entrypoint": "BitBybitWebCadFacade", + "projectRuntime": { "node": "22.23.2", "npm": "10.9.8", "source": "Node.js official distribution", "sha256Manifest": "config/node-runtime.env", "execution": "project-local .runtime; system Node is not used" }, "frontendFramework": "React 18 + Vite 6 + TypeScript 5", "geometry": { "provider": "@bitbybit-dev/occt-worker", "kernel": "OCCT WASM", "version": "1.1.1", "execution": "dedicated worker", "wasmSha256": "1b6a8fc7b83d222854f73b66af8d0e45ad4fdee025904b447e885d4e18ae8656", "status": "core-feature-integration" }, "viewport": { "provider": "Three.js internal adapter", "package": "three", "version": "0.185.1", "baseline": "WebGL2", "status": "occt-mesh-integration" }, diff --git a/docs/freecad-full-parity-plan.zh-CN.md b/docs/freecad-full-parity-plan.zh-CN.md new file mode 100644 index 0000000..575fa16 --- /dev/null +++ b/docs/freecad-full-parity-plan.zh-CN.md @@ -0,0 +1,305 @@ +# 完整 FreeCAD 对标实施方案 + +> 本文是“功能完全对标 FreeCAD”的工程基线和任务分解,不把当前已有的页面、MockFacade 或少量 OCCT 特征误报为完成。所有“支持”都必须经过锁定版本 FreeCAD 的行为黄金测试和文件互操作测试。 + +## 1. 目标与边界 + +### 1.1 目标 + +以 FreeCAD `1.1.1` 官方发布标签为首个兼容基线,精确源码提交为 `0108fd4b4850cc46e625b60e53cea7a7bbe69f8d`。实现可在浏览器运行的 CAD 应用:React 负责 FreeCAD 风格界面,Three.js 最新锁定版本负责三维显示,Bitbybit 是唯一入口,FreeCAD/OCCT 业务逻辑在 WASM Worker 中运行,SQLite WASM + OPFS 负责项目、文档、对象、事务和资源存储。 + +### 1.2 唯一入口原则 + +对 React 和页面公开的唯一业务入口是 `BitBybitWebCadFacade`。页面不得直接导入 Three.js、OCCT、OCCT Worker、SQLite 或 OPFS;这些实现只能由 Facade 适配器和 Worker 使用。新增工作台、命令、属性、文件格式和诊断,必须先扩展版本化 Facade 合同,再接入页面。 + +### 1.3 “完整对标”的可验收定义 + +| 等级 | 含义 | 允许的产品表述 | +|---|---|---| +| `exact` | 与基线 FreeCAD 在输入、状态、结果、错误、选择和持久化语义上逐项一致 | “已对标” | +| `compatible` | 结果和核心交互一致,允许明确记录的浏览器呈现差异 | “兼容” | +| `read-only` | 可读取、可显示、可导出,但不能安全编辑和重算 | “只读” | +| `proxy` | 保留未知对象、属性或 Shape 资产,不能保证语义重算 | “代理” | +| `unsupported` | 有意拒绝并生成结构化诊断 | “不支持” | + +没有黄金测试、差异报告和迁移策略的功能,不得标记为 `exact` 或 `compatible`。浏览器限制必须体现为能力矩阵和用户可见诊断,不能静默删除 FreeCAD 行为。 + +## 2. 当前差距与解决策略 + +| 领域 | 当前切片 | 达到对标还缺少的核心能力 | 主任务包 | +|---|---|---|---| +| 拓扑命名 | 有 `SubshapeRef` 类型和句柄生命周期 | 历史映射、持久名称、歧义检测、版本迁移 | TSN | +| Sketcher | 有轮廓输入验证 | 几何/约束模型、Planegcs 求解、编辑器交互、外部几何 | SK | +| Expression/单位 | Property metadata 可声明单位 | 词法/语法、Quantity、维度检查、引用、循环和 locale | EXU | +| 依赖 DAG/重算 | 有事务和版本号 | Link 图、SCC、调度、取消、增量结果、错误传播 | DAG | +| Part/PartDesign | 已有部分 Primitive/Boolean/Pad/Pocket/Revolution | 完整特征、Body/Tip、附着、模式、失败恢复 | PD/PART | +| 工作台 | manifest 和界面骨架 | Draft、TechDraw、Spreadsheet、Assembly、BIM、CAM、FEM、Mesh 等业务 | WB | +| 文件 | 项目 schema v1、资源对象 | FCStd 读写、BREP/GuiDocument、扩展/代理、格式导入导出 | FC | +| 质量 | Facade Node 测试和部分浏览器矩阵 | FreeCAD 对照回放、几何黄金、压力、跨浏览器和安全门禁 | QA | + +## 3. 总体架构与不可破坏的约束 + +### 3.1 分层 + +1. **Presentation**:React 页面、FreeCAD 菜单/工具栏、Combo View、Tasks、Property editor、Report view、快捷键和无障碍。 +2. **Facade**:`App/Gui/Workbench/Command/Selection/Task/Project/Viewport` 合同、权限、前置条件、请求上下文、诊断、事件和版本。 +3. **Domain kernel**:Document、DocumentObject、Property、Link、Expression、Sketch、Feature、Body/Tip、事务、DAG、重算。 +4. **Geometry**:Bitbybit OCCT API 和必要的 FreeCAD/OCCT C++ 模块 WASM 化;B-Rep 句柄仅在 Worker 内部存在。 +5. **Workers**:几何、求解、重算、持久化各自独占 Worker;消息带 `requestId`、`documentId`、`documentVersion`、取消和诊断。 +6. **Persistence**:SQLite WASM OPFS 单写者、项目迁移、资源内容寻址、事务日志、恢复和导入导出。 + +### 3.2 领域不变量 + +- 对象 UUID、内部名称和子形状名称永不使用显示 Label 代替。 +- 所有长度、角度、面积、体积在内部使用有维度的 Quantity;数据库保存规范单位和原始表达式。 +- 任何重算结果只有在请求版本仍等于当前文档版本时才能提交;过期结果必须丢弃。 +- 依赖循环、求解冲突、拓扑歧义和未知文件对象必须显式失败并保留诊断。 +- Undo/Redo、自动保存和资源引用计数必须以同一事务边界提交。 +- Worker 结果不可直接改变 React 状态,必须经 Facade 校验和事件投影。 + +## 4. 专项一:稳定子形状命名(TSN) + +### 4.1 目标 + +实现与 FreeCAD 特征历史可比的 `TopoRef`:面、边、顶点、壳、实心体和 Compound 在特征重算、参数编辑、保存/加载、布尔操作和导出后仍能被稳定引用;无法唯一判断时返回 `ambiguous`,绝不把错误的面静默绑定给另一个面。 + +### 4.2 技术措施 + +1. 在 OCCT Worker 内保留每次特征的输入 Shape、输出 Shape、历史对象和拓扑签名;使用 `Generated/Modified/Deleted` 历史关系生成映射。 +2. 为每个子形状计算规范签名:拓扑类型、几何类型、解析几何参数、几何中心/长度/面积/体积、邻接度数、方向、局部坐标和量化容差。签名必须区分“用于匹配的特征”与“仅用于诊断的数值”。 +3. 采用分层命名:`documentId/objectUuid/featureRevision/subshapeKind/localOrdinal`,再绑定跨版本映射记录;不使用 OCCT transient hash 或三角网格索引作为持久 ID。 +4. 对对称、重复和布尔切分结果建立候选集合和评分;最高分不超过唯一阈值时返回歧义,并在 UI 的选择/任务面板显示候选。 +5. 保存 `TopoRef` 的来源特征版本、签名、映射算法版本和容差;加载旧版本先迁移,迁移失败保留原引用和报告。 +6. 将面/边选择、附着、Fillet/Chamfer、Pocket up-to-face、TechDraw 投影、测量和表达式引用全部改用 `TopoRef`。 + +### 4.3 任务分解 + +| ID | 任务与交付物 | 前置 | 验收 | +|---|---|---|---| +| TSN-01 | 从基线 FreeCAD 采集拓扑命名、选择和错误行为 | P0 | 30 个模型有输入/输出映射 | +| TSN-02 | 定义 `TopoRef`、状态和迁移 JSON Schema | P1 | exact/ambiguous/deleted 均可序列化 | +| TSN-03 | OCCT 历史捕获适配器 | P3 | Pad/Pocket/Boolean 产生 Generated/Modified/Deleted 集 | +| TSN-04 | 规范几何/邻接签名库 | TSN-02 | 相同 B-Rep 重放签名稳定,网格精度变化不影响 | +| TSN-05 | 一对一匹配和评分算法 | TSN-03/04 | 平移、参数编辑和重算保持引用 | +| TSN-06 | 对称/重复拓扑歧义检测 | TSN-05 | 低置信度拒绝错误绑定并给出候选 | +| TSN-07 | 布尔与多实体历史映射 | TSN-03 | Union/Cut/Common 后选择引用可迁移 | +| TSN-08 | 引用持久化、版本迁移和回滚 | FC-02 | 保存/加载后 UUID 与状态不变 | +| TSN-09 | Facade 选择、附着和任务 API 接入 | TSN-02 | React 无内部拓扑 API 导入 | +| TSN-10 | 拓扑黄金回放与随机变异测试 | QA-02 | 100 个黄金模型、1000 次参数变异通过 | + +## 5. 专项二:Sketcher 求解器(SK) + +### 5.1 目标 + +达到 FreeCAD Sketcher 的约束建模和交互语义:几何创建/编辑、约束、自由度、冗余/冲突诊断、拖拽、外部几何、支持的曲线类型、B-spline、块约束、构造几何、投影和编辑事务。 + +### 5.2 技术路线 + +- 优先将 FreeCAD Sketcher 的 `planegcs`/相关 C++ 求解器以独立 WASM 模块编译,固定编译器、Emscripten、浮点和异常策略;Bitbybit Facade 只调用类型化求解服务。 +- 若某一模块无法直接编译,先做等价 C++/WASM 端口并建立逐案例差异报告;禁止用页面 JavaScript 中的近似求解器冒充兼容。 +- 求解 Worker 采用确定性输入排序、固定容差、最大迭代次数和取消点;输出几何、自由度、约束状态、冲突集合、残差和诊断。 + +### 5.3 任务分解 + +| ID | 任务与交付物 | 前置 | 验收 | +|---|---|---|---| +| SK-01 | 采集 Sketcher 几何/约束/快捷键/错误 manifest | P0 | 覆盖所有基线命令和约束类型 | +| SK-02 | 定义 Sketch 文档对象、几何索引和构造标志 | P1 | SQLite/Facade 可往返 | +| SK-03 | 编译 planegcs 求解器 WASM POC | P0-RT | 直线/圆/弧约束结果与 FreeCAD 一致 | +| SK-04 | 确定性求解协议和 Worker 生命周期 | SK-03 | 取消/过期版本不会提交 | +| SK-05 | 基础几何 API:点、线、圆、弧、椭圆、B-spline | SK-02 | 编辑/撤销/重做完整 | +| SK-06 | 基础约束:Coincident、Horizontal、Vertical、Distance、Angle | SK-04/05 | 自由度和残差黄金一致 | +| SK-07 | 高级约束:Tangent、Equal、Symmetric、Block、Diameter、Radius | SK-06 | 冲突/冗余分类一致 | +| SK-08 | 外部几何、构造几何和投影 | TSN-09 | 选择引用可稳定重算 | +| SK-09 | 拖拽、自动约束、约束编辑器和任务面板 | SK-06 | 鼠标/键盘回放与桌面行为一致 | +| SK-10 | B-spline 编辑、节点/权重和约束限制 | SK-07 | 支持范围明确,无静默降级 | +| SK-11 | Sketch 支持 Pad/Pocket/Revolution 和附着面 | TSN-07/DAG-07 | 轮廓修改触发正确重算 | +| SK-12 | Sketch 黄金文件和求解压力套件 | SK-01..11 | 500 个模型、冲突/欠约束/过约束覆盖 | + +## 6. 专项三:Expression 与单位系统(EXU) + +### 6.1 目标 + +与 FreeCAD Quantity/Expression 语义对齐:单位后缀、复合单位、维度检查、对象/属性引用、Spreadsheet 别名、函数、常量、locale 显示、表达式错误和循环诊断。数据库必须同时保留规范 Quantity 与用户输入表达式。 + +### 6.2 技术措施 + +1. 词法、解析、类型检查和求值在独立 Worker 完成;UI 只显示 token、候选和诊断。 +2. 采用有理数/高精度数值表示可精确保存单位换算;几何内核边界再转换为固定精度浮点。 +3. 单位注册表包含维度向量、规范单位、别名、比例/偏移转换和显示格式;所有单位表有版本号。 +4. 表达式解析产生依赖边,和对象 Link 一起进入统一 DAG;禁止通过字符串扫描绕开依赖和循环检测。 +5. 序列化使用规范 locale(`.` 小数、显式单位 ID),显示 locale 只在 Presentation 层转换。 + +### 6.3 任务分解 + +| ID | 任务与交付物 | 前置 | 验收 | +|---|---|---|---| +| EXU-01 | 采集 FreeCAD 单位 schema、表达式函数和错误 | P0 | 建立可机器读取清单 | +| EXU-02 | Quantity/Unit/Dimension 类型与 SQLite schema | P1/FC-02 | 长度、角度、面积、体积可往返 | +| EXU-03 | 表达式 tokenizer/parser AST | EXU-01 | 运算符优先级和函数错误一致 | +| EXU-04 | 维度推导、转换和舍入规则 | EXU-02/03 | `mm+inch`、角度/长度混用按基线拒绝 | +| EXU-05 | 对象属性、Spreadsheet alias 和子形状引用 | TSN-02/DAG-02 | 引用重命名不丢失 | +| EXU-06 | 循环、未知符号、过期引用诊断 | DAG-04 | 错误包含对象/属性/表达式位置 | +| EXU-07 | locale 显示、自动完成和属性编辑器 | EXU-03 | 显示变化不改变规范序列化 | +| EXU-08 | 表达式迁移、导入导出和黄金测试 | FC-05 | 1000 条表达式跨版本一致 | + +## 7. 专项四:依赖 DAG 与重计算(DAG) + +### 7.1 目标 + +实现 FreeCAD 文档对象的依赖、Touched/Copy/Recompute 状态和事务语义:Link、Expression、子形状、Body/Tip、外部文档和视图依赖都进入统一有向图;重算按稳定拓扑序执行,循环和失败按对象传播。 + +### 7.2 技术措施 + +- 图节点为 `DocumentObject` 的计算版本,边包含 Link、Expression、TopoRef、Container 和 View 依赖类型。 +- 每次参数事务生成 immutable snapshot、dirty set 和 recompute generation;使用 Tarjan/Kosaraju 检测 SCC,循环节点不运行几何。 +- 调度器按依赖层级把可并行节点分发给 Geometry/Solver Worker;结果带 generation 和输入 hash,只有匹配时提交。 +- 一个节点失败不覆盖上一次有效 Shape;下游进入 `UpstreamFailed`,Report view 可定位根因。 +- Undo/Redo 恢复参数、图、状态和 Shape 资源引用;保存只提交已确认版本。 + +### 7.3 任务分解 + +| ID | 任务与交付物 | 前置 | 验收 | +|---|---|---|---| +| DAG-01 | Link/Expression/TopoRef 统一边模型 | TSN-02/EXU-05 | 图可解释每条边的来源 | +| DAG-02 | 对象注册、删除传播和反向索引 | P4-01 | 删除前显示受影响对象 | +| DAG-03 | Touched/Up-to-date/Error 状态机 | P4-04 | 状态转换与 FreeCAD 回放一致 | +| DAG-04 | SCC 循环检测和诊断 | DAG-01 | 循环不进入 OCCT/Sketch 求解 | +| DAG-05 | 稳定拓扑排序和并行层 | DAG-04 | 相同输入顺序可复现 | +| DAG-06 | generation、取消和过期结果丢弃 | P1-03 | 快速连续编辑不回写旧结果 | +| DAG-07 | Body/Tip、容器、Feature 链和支持映射 | DAG-05/TSN-07 | PartDesign 链正确显示 Tip | +| DAG-08 | 失败保留、下游传播和恢复 | DAG-07 | 修复上游后仅重算必要节点 | +| DAG-09 | 事务/Undo/Redo/Autosave 原子提交 | P2-04 | 崩溃恢复不出现半个图 | +| DAG-10 | DAG 可视化、诊断和性能计数器 | DAG-08 | 用户可定位耗时/失败节点 | +| DAG-11 | 1000 对象、长链、宽图、随机图基准 | DAG-01..10 | 记录内存、吞吐、取消和尾延迟 | + +## 8. 专项五:核心业务对象与剩余工作台(WB) + +### 8.1 共通实施规则 + +每个工作台都必须完成四份资产:`workbench-manifest.json`(菜单、命令、快捷键、任务面板和前置条件)、对象/属性 schema、Facade API、FreeCAD 黄金回放集。只有页面按钮存在而命令状态、选择过滤、重算和文件兼容未完成时,状态仍为 `ui-only`。 + +### 8.2 工作台分解 + +| 工作台 | 业务范围 | 主要任务 | 进入条件 | +|---|---|---|---| +| PartDesign | Body、基准、草图、Pad/Pocket、Dress-up、Pattern、Boolean、Additive/Subtractive | PD-01..PD-18:附着、支持面、两长度、Draft、Fillet/Chamfer、Hole、Pattern、Thickness、Tip | SK、TSN、DAG | +| Part | 原语、布尔、管道、阵列、变换、检查/修复、导入导出 | PART-01..PART-16:ShapeBinder、CompSolid、Refine、Make compound、容差和修复 | TSN、DAG | +| Sketcher | 见 SK 专项 | SK-01..SK-12 | Solver WASM | +| Draft | 2D 绘图、尺寸、阵列、图层、Working Plane、BIM 辅助 | DRAFT-01..DRAFT-14:吸附、网格、文本、样条、偏移和参数化 | EXU、DAG | +| TechDraw | 页面、视图、投影、尺寸、模板、SVG/PDF 导出 | TD-01..TD-15:隐藏线、剖视、链接视图、模板兼容和打印比例 | TSN、FC | +| Spreadsheet | 单元格、公式、别名、格式、链接和导出 | SS-01..SS-10:表达式依赖、循环、CSV/XLSX 边界和大表性能 | EXU、DAG | +| Assembly | 组件、连接器、约束、求解、变体、BOM | ASM-01..ASM-16:组件链接、joint solver、装配树、碰撞和导出 | DAG、TSN | +| BIM/Arch | 结构/建筑对象、材料、楼层、墙/梁/窗、IFC | BIM-01..BIM-20:属性集、族、空间、IFC2x3/IFC4 映射和代理 | FC、EXU | +| FEM | 材料、网格、边界、求解器、结果 | FEM-01..FEM-18:网格接口、求解器 WASM 构建、结果场和单位 | EXU、WB 安全门 | +| Mesh | 网格导入、修复、布尔、简化、分析和导出 | MESH-01..MESH-14:法向、非流形、误差、LOD 和大网格 Worker | FC、QA | +| Surface | 曲面创建、偏移、放样、填充、修剪、缝合 | SURF-01..SURF-14:NURBS/BSpline 参数、边界和缝合容差 | OCCT 扩展、TSN | +| CAM | Job、刀具、路径、后处理、仿真和 G-code | CAM-01..CAM-20:刀具库、碰撞、后处理器沙箱、单位和输出黄金 | FEM/Mesh 可选,安全门 | +| Inspection/Measure | 测量、剖切、偏差、标注和报告 | INSP-01..INSP-10:TopoRef 选择、精度、报告和导出 | TSN、EXU | +| Raytracing/Robot/其他 | 基线版本清单中的其余官方模块 | WB-REST-01..WB-REST-20:逐模块决定 WASM 端口或明确不支持 | P0 manifest | + +### 8.3 工作台交付顺序 + +先完成 `PartDesign + Part + Sketcher + TechDraw + Spreadsheet` 的参数化闭环,再做 Draft/Assembly/BIM/Mesh/Surface,最后处理 FEM/CAM/Raytracing 等需要额外数值库或后处理器的模块。每个工作台在进入下一层前必须通过对象创建、编辑、重算、撤销、保存、加载和导出七项门禁。 + +## 9. 专项六:文件兼容与迁移(FC) + +### 9.1 兼容范围 + +1. **FCStd 读取**:ZIP 容器、`Document.xml`、`GuiDocument.xml`、BRep/Shape 资源、缩略图、对象属性、Expression、Link、视图状态和未知扩展。 +2. **FCStd 写入**:生成 FreeCAD 1.1.1 可打开的最小等价文件;写入前完成 schema、内核和工作台能力检查。 +3. **项目格式**:继续维护 `.webcad` manifest、SQLite schema、OPFS 资源包和迁移版本;FCStd 与 `.webcad` 不混用语义。 +4. **交换格式**:STEP/IGES/BREP、STL/OBJ/PLY、SVG/PDF、DXF、glTF、IFC、CSV;每种格式记录精度、颜色、单位、拓扑和只读限制。 +5. **未知对象**:保留原始 XML/资源和 proxy Shape,显示兼容报告;禁止执行 FCStd 中的 Python、宏或任意代码。 + +### 9.2 任务分解 + +| ID | 任务与交付物 | 前置 | 验收 | +|---|---|---|---| +| FC-01 | 采集 FCStd 文件结构和 FreeCAD 读写样例 | P0 | 每个核心对象有最小/复杂样例 | +| FC-02 | `.webcad`/SQLite schema v2 与迁移框架 | P2 | 迁移可回滚、旧版本可读 | +| FC-03 | ZIP 安全读取、大小/路径/压缩比限制 | FC-01 | zip bomb、路径穿越、恶意 XML 被拒绝 | +| FC-04 | Document/GuiDocument XML 映射 | P4/DAG | 树、属性、视图状态可往返 | +| FC-05 | BRep/Shape 资源读写和精度策略 | P3 | 体积、拓扑数和几何容差有报告 | +| FC-06 | Expression/Unit/Link/TopoRef 序列化 | EXU/TSN/DAG | 保存后可重算或明确 proxy | +| FC-07 | 未知对象/扩展 proxy 和差异报告 | FC-04 | 不丢原始资源、不假称可编辑 | +| FC-08 | STEP/IGES/BREP 导入导出 | PART | 100 个模型几何黄金通过 | +| FC-09 | Mesh/DXF/SVG/PDF/glTF/IFC/CSV 适配 | WB | 单位、颜色、精度和失败诊断正确 | +| FC-10 | FreeCAD↔WebCAD round-trip 测试 | FC-04..09 | 读→改→写→读的语义差异可解释 | +| FC-11 | 大文件、损坏文件和中断恢复 | FC-03 | 2 GB 级别按能力矩阵降级,不崩溃 | + +## 10. 实施阶段与依赖 + +| 阶段 | 必须完成的结果 | 依赖 | 阶段门 | +|---|---|---|---| +| F0 基线冻结 | FreeCAD 提交/构建容器、Bitbybit/OCCT、Three、SQLite、浏览器、许可证和黄金样例 | 无 | G0:版本、来源、SBOM 完整 | +| F1 Facade/文档核心 | DocumentObject、Property、事务、选择、任务、诊断和 schema | F0 | G1:React 仅能通过 Facade 编译 | +| F2 表达式/单位/DAG | Quantity、Expression、统一依赖图、SCC、重算和恢复 | F1 | G2:1000 对象图和循环/错误黄金通过 | +| F3 稳定拓扑命名 | 历史映射、歧义、安全引用和迁移 | F2 + OCCT | G3:参数编辑/布尔/保存加载引用稳定 | +| F4 Sketcher | Solver WASM、约束、编辑器、外部几何和轮廓输出 | F2/F3 | G4:欠约束/冲突/拖拽/回放通过 | +| F5 Part/PartDesign 闭环 | Body/Tip、基准、特征、附着、DAG、Undo、FCStd | F3/F4 | G5:核心参数化建模闭环 | +| F6 TechDraw/Spreadsheet/Draft | 页面、投影、公式表、2D 参数化 | F2/F3/F5 | G6:核心生产文档可保存导出 | +| F7 Assembly/BIM/Mesh/Surface | 工作台对象、求解/几何和互操作 | F3/F5/F6 | G7:各工作台七项门禁 | +| F8 FEM/CAM/其余模块 | 数值库、后处理器、仿真和明确能力矩阵 | F0/F7 | G8:每个模块 exact/compatible/read-only/proxy | +| F9 发布验收 | 全量回放、跨浏览器、性能、安全、迁移和回滚 | F0..F8 | G9:无未解释的 unsupported 核心路径 | + +## 11. 详细任务包(跨领域共通) + +| ID | 任务 | 交付物 | 验收标准 | +|---|---|---|---| +| P0-01 | 固定 FreeCAD 源码、编译器和依赖镜像 | lock、容器、构建日志 | 同一输入产生相同版本信息 | +| P0-02 | 生成 UI/命令/对象/Property/工作台 manifest | JSON manifest | 每条能力有来源和等级 | +| P0-03 | 建立 100 个黄金文件、50 个错误文件、回放脚本 | fixtures、expected | 桌面结果可复现 | +| P0-04 | 固定浏览器能力矩阵和 COOP/COEP | browser matrix | Chrome/Firefox/Safari 最新两个版本有结果 | +| P0-05 | 许可证、供应链、WASM 安全评审 | SBOM、许可证清单 | 无未知二进制和高风险漏洞 | +| API-01 | Facade API 版本化和 JSON Schema | `src/facade` 合同 | 破坏性变更有迁移和弃用期 | +| API-02 | 统一请求上下文、取消、进度和诊断 | protocol types | 过期结果无法提交 | +| API-03 | 事件溯源与状态投影 | event log/reducer | 刷新后状态可重建 | +| APP-01 | Document/Object/Property 完整模型 | schema + adapter | Data/View、只读、隐藏、枚举、Link 全覆盖 | +| APP-02 | Transaction/Undo/Redo/Autosave 原子化 | transaction service | 崩溃点恢复无半提交 | +| APP-03 | Body/Container/Group/Link/Extension | object types | 树与依赖图一致 | +| GEO-01 | OCCT C++/WASM 构建和版本隔离 | build scripts | 资源、符号、许可证可追溯 | +| GEO-02 | Shape 生命周期、引用计数和 Worker 回收 | handle service | 长会话无不可控增长 | +| GEO-03 | B-Rep→Three 网格、选择和增量缓存 | viewport adapter | 选择不重建无关对象 | +| GEO-04 | 几何精度、容差和确定性 | tolerance policy | 体积/面积/拓扑黄金稳定 | +| QA-01 | 单元/属性/解析器/图算法测试 | tests | 分支和错误分类达标 | +| QA-02 | FreeCAD CLI/GUI 回放对照器 | comparator | 差异含输入、版本、对象和数值 | +| QA-03 | 浏览器 E2E、视觉和几何黄金 | Playwright fixtures | 三浏览器能力结果可审计 | +| QA-04 | 性能/内存/取消/恢复压力 | benchmark reports | P95、峰值内存、失败率达门槛 | +| QA-05 | 安全测试和 fuzzing | corpus/reports | 文件、表达式、Worker 消息不可越权 | + +## 12. 每个功能的开发模板 + +1. 在基线 manifest 登记 FreeCAD 命令、对象、属性、选择前置和错误。 +2. 先写 Facade 类型、请求上下文、诊断和事件,再写 Worker/domain 实现。 +3. 添加最小成功、失败、取消、Undo/Redo、保存/加载和过期结果测试。 +4. 建立 FreeCAD 对照 fixture,记录几何容差、单位、拓扑和状态差异。 +5. 接入 React 页面和 FreeCAD 风格任务面板;页面不直接访问内部实现。 +6. 更新 SQLite migration、`.webcad`/FCStd 映射、兼容矩阵和文档。 +7. 通过阶段门后才把状态从 `experimental` 提升为 `compatible` 或 `exact`。 + +## 13. 质量门禁与发布阻断条件 + +发布必须同时满足:`./npmw run verify`、Facade-only 依赖守卫、所有核心黄金回放、FCStd round-trip、跨浏览器 OPFS/Worker、几何精度、长会话内存、安全 fuzzing 和许可证检查。下列任一项阻断发布: + +- 旧版本/未知对象被静默删除或当作可编辑对象; +- 过期 Worker 结果覆盖新版本文档; +- 子形状歧义被强行绑定; +- Expression 单位错误、循环或 locale 转换被静默吞掉; +- FCStd 读取后保存造成未报告的对象、属性、Shape 或视图丢失; +- 任一页面绕过 `BitBybitWebCadFacade` 导入内部 Worker、OCCT、Three 或 SQLite; +- 性能/内存超出能力矩阵而没有明确降级和诊断。 + +## 14. 下一轮实际执行顺序 + +1. 使用项目本地 Node 运行时完成 P0-RT、依赖安装和验证,清除引擎警告。 +2. 完成 P0-01/P0-03:FreeCAD 提交、构建容器、manifest 和黄金模型目录。 +3. 实现 `DAG-01..DAG-06` 与 `EXU-01..EXU-06`,让属性修改从“Touched 标记”升级为可解释的依赖重算。 +4. 实现 `TSN-02..TSN-07`,再把附着、Pocket up-to-face、测量和 TechDraw 选择切换到 `TopoRef`。 +5. 编译/验证 `SK-03`,然后按基础约束→高级约束→编辑器→PartDesign 轮廓的顺序推进。 +6. 以 PartDesign 核心闭环为第一条完整垂直链,随后接入 FCStd 读写和 round-trip。 +7. 按工作台表逐个实现并验收,任何暂未端口的模块保持 `read-only/proxy/unsupported`,不提前宣称完整。 + +## 15. 完成定义 + +“完整 FreeCAD 对标”只有在 F0-F9 全部阶段门通过、核心与扩展工作台均有兼容等级、稳定子形状命名/Sketcher/Expression/单位/DAG/重算/FCStd 均有黄金报告、跨浏览器和安全门禁通过,并且发布文档列出所有差异后才能使用。当前仓库仍处于核心 Facade、OCCT 几何和属性编辑的进行中阶段,下一工作目标按第 14 节执行。 diff --git a/docs/project-runtime.zh-CN.md b/docs/project-runtime.zh-CN.md new file mode 100644 index 0000000..73e5e9e --- /dev/null +++ b/docs/project-runtime.zh-CN.md @@ -0,0 +1,90 @@ +# 项目运行时基线与“引擎版本警告”解决方案 + +## 1. 问题结论 + +本项目的 `package.json` 声明 Node.js `>=22`,而原开发环境实际使用系统 `/usr/bin/node v20.19.2` 与 `/usr/bin/npm 9.2.0`。npm 的 `EBADENGINE` 是运行 npm 的解释器版本不满足依赖声明所产生的警告;仅修改 `engines`、PATH 文档或 Vite 配置都不会改变当前解释器。 + +项目现在锁定 Node.js `22.23.2` 与 npm `10.9.8`,并把官方归档下载到被 Git 忽略的 `.runtime/`。`./npmw`、`./nodew` 和 `scripts/bootstrap-node.sh` 是项目运行时的唯一启动路径,系统 Node 不会被替换或修改。 + +版本与校验值来源: + +- [Node.js v22.23.2 官方发行目录](https://nodejs.org/dist/v22.23.2/) +- [Node.js 官方 SHA-256 清单](https://nodejs.org/dist/v22.23.2/SHASUMS256.txt) +- [Node.js 官方发行索引](https://nodejs.org/dist/index.json) + +## 2. 固定契约 + +| 项目 | 固定值 | 记录位置 | 目的 | +|---|---|---|---| +| Node | `22.23.2` | `.node-version`、`.nvmrc`、`config/node-runtime.env` | 规避同一大版本内的隐式差异 | +| npm | `10.9.8` | `package.json` 的 `packageManager`、`config/node-runtime.env` | 固定 lockfile 生成器 | +| Node 来源 | `nodejs.org` 官方归档 | `scripts/bootstrap-node.sh` | 下载来源可审计 | +| 完整性 | 四平台 SHA-256 | `config/node-runtime.env` | 防止缓存或代理返回错误二进制 | +| 安装目录 | `.runtime/` | `.gitignore` | 不把二进制提交到仓库 | +| 包引擎策略 | `engine-strict=true` | `.npmrc` | 误用旧 Node 时立即失败,不再静默产生警告 | +| 应用入口 | `BitBybitWebCadFacade` | `config/runtime-baseline.json` | React、Three、SQLite、OCCT 均不能绕过 Facade | + +## 3. 日常操作 + +首次安装或依赖变化: + +```bash +./npmw install +``` + +启动开发服务器: + +```bash +./npmw run dev +``` + +全量校验: + +```bash +./npmw run verify +``` + +直接查看项目解释器: + +```bash +./nodew --version +./npmw --version +``` + +期望输出分别为 `v22.23.2` 和 `10.9.8`。第一次运行会下载约 30 MB 的 Node Linux x64 归档,后续运行复用校验通过的缓存。 + +## 4. 启动器的安全行为 + +1. 脚本按 `uname` 映射 Linux x64/arm64、macOS x64/arm64,平台不在清单内时明确失败。 +2. 下载先写入 `.part` 文件,下载完成后计算 SHA-256,校验通过才原子改名为归档文件。 +3. 已有缓存每次启动仍检查 SHA-256;错误缓存只删除确定的单个归档路径,不触碰仓库或用户目录。 +4. 解压到 `.runtime/extract.*` 临时目录,确认 `bin/node` 存在且 `node --version` 精确等于 `v22.23.2` 后才移动到目标目录。 +5. 运行时二进制和下载缓存不进入 Git;升级必须同时更新版本、四个平台校验值、lockfile 和验证记录。 + +## 5. CI 与发布规则 + +- CI 不直接调用系统 `npm`,统一使用 `./npmw ci`、`./npmw run verify`。 +- CI 缓存键必须包含 Node 版本、操作系统、架构和 `package-lock.json` 哈希。 +- 合并门禁先执行 `./nodew scripts/check-runtime.mjs`,再执行 Facade 边界检查、单元测试、构建和浏览器黄金测试。 +- 发布产物必须记录 Node/npm、Vite、Three.js、Bitbybit OCCT、SQLite WASM 版本及 SHA-256;禁止使用 `npm install` 生成未审计 lockfile。 +- Node 升级是独立变更:先在 P0-RT-01 建立双版本矩阵,再更新 `.node-version` 与校验值,最后重新生成 lockfile 和 SBOM。 + +## 6. 故障处理 + +**仍出现 `EBADENGINE`**:检查命令是否以 `./npmw` 开头;检查 `./nodew --version`。直接执行系统 `npm` 在 `.npmrc` 的 `engine-strict=true` 下应失败,这是故意的防误用信号。 + +**下载失败**:确认能访问 `https://nodejs.org`,删除确定的 `.runtime/downloads/node-v22.23.2-*` 缓存后重新执行 `./npmw install`。不要手工替换归档或跳过校验。 + +**架构不支持**:在当前开发机使用 Linux x64;新增平台时必须先把官方归档和 SHA-256 加入 `config/node-runtime.env`,并增加对应 CI job。 + +**依赖损坏**:执行 `./npmw ci` 重建 `node_modules`,不要用系统 Node 生成或修改 lockfile。 + +## 7. 验收证据 + +运行时修复的完成条件是: + +- `./nodew --version` 与 `./npmw --version` 精确匹配固定值; +- `./npmw install` 无 `EBADENGINE`; +- `./npmw run verify` 中的 `check:runtime`、Facade 边界、测试和生产构建全部通过; +- Vite/tsx 子进程的 `/proc//exe`(Linux)指向 `.runtime/node-v22.23.2-*`; +- 旧 `/usr/bin/node` 不被替换,且仓库工作区无 `.runtime` 未跟踪文件。 diff --git a/docs/web-cad-implementation-plan.zh-CN.md b/docs/web-cad-implementation-plan.zh-CN.md index 7ee591c..42472c4 100644 --- a/docs/web-cad-implementation-plan.zh-CN.md +++ b/docs/web-cad-implementation-plan.zh-CN.md @@ -1699,3 +1699,9 @@ DocumentSnapshot 现在区分模型树投影与 `DocumentObjectSnapshot` 真值 持久化复用 schema v1 已有 `object_properties` 表:`value_json` 保存完整 Property snapshot,`property_type` 保留可查询类型;删除/重写对象时依赖外键级联清理旧属性。内存降级、autosave、Undo/Redo 和 Facade state 均使用深拷贝,避免 options/property 数组共享引用。 当前限制:Expression 只展示 metadata,尚无解析/依赖求值;Length 以文档 mm 基值保存,尚无 Quantity 单位换算;PropertyLink 只校验本 Document 目标存在,未实现循环和删除传播;多选 mixed、批量编辑、重置默认值和属性搜索仍由 P4-02/P6-02 后续任务完成。属性变更只做轻量 Touched 标记,不在 UI 线程同步执行 OCCT 重计算。 + +### 16.16 引擎版本修复与完整 FreeCAD 对标专项 + +本项目原先以系统 Node `v20.19.2` 执行 npm,而依赖要求 Node 22,因而出现 `EBADENGINE`。现已增加项目内、SHA-256 校验的 Node `22.23.2`/npm `10.9.8` 运行时;日常命令统一使用 `./npmw` 或 `./nodew`,系统解释器不被修改。详见 [项目运行时基线](project-runtime.zh-CN.md)。 + +“完整 FreeCAD 对标”不等于已有页面或少量 OCCT 特征。稳定子形状命名、Sketcher 求解器、Expression/单位、依赖 DAG/重计算、剩余工作台和 FCStd/交换格式兼容已拆成独立的可验收任务包、阶段门和黄金测试,详见 [完整 FreeCAD 对标实施方案](freecad-full-parity-plan.zh-CN.md)。FreeCAD `1.1.1` 标签已核对并记录精确提交;构建参数和 WASM 端口仍须通过该方案的 F0 基线门后才能宣称兼容。 diff --git a/nodew b/nodew new file mode 100755 index 0000000..a982db9 --- /dev/null +++ b/nodew @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)" +runtime_dir="$("$repo_dir/scripts/bootstrap-node.sh")" +exec "$runtime_dir/bin/node" "$@" diff --git a/npmw b/npmw new file mode 100755 index 0000000..133fcec --- /dev/null +++ b/npmw @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd -P)" +runtime_dir="$("$repo_dir/scripts/bootstrap-node.sh")" +export PATH="$runtime_dir/bin:$PATH" +exec "$runtime_dir/bin/npm" "$@" diff --git a/package-lock.json b/package-lock.json index 01ce324..9b9f000 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,7 @@ "vite": "^6.0.5" }, "engines": { - "node": ">=22" + "node": "22.23.2" } }, "node_modules/@babel/code-frame": { diff --git a/package.json b/package.json index 09e0089..f586e71 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,17 @@ "version": "0.1.0", "type": "module", "engines": { - "node": ">=22" + "node": "22.23.2" }, + "packageManager": "npm@10.9.8", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "preview": "vite preview", "check:facade-boundary": "node scripts/check-facade-boundary.mjs", "test:facade": "tsx --test tests/facade.test.ts", - "verify": "npm run check:facade-boundary && npm run test:facade && npm run build" + "check:runtime": "node scripts/check-runtime.mjs", + "verify": "npm run check:runtime && npm run check:facade-boundary && npm run test:facade && npm run build" }, "dependencies": { "@bitbybit-dev/occt": "1.1.1", diff --git a/scripts/bootstrap-node.sh b/scripts/bootstrap-node.sh new file mode 100755 index 0000000..cbbd8d6 --- /dev/null +++ b/scripts/bootstrap-node.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_dir="$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd -P)" +# shellcheck disable=SC1091 +. "$repo_dir/config/node-runtime.env" + +runtime_root="$repo_dir/.runtime" +mkdir -p "$runtime_root/downloads" + +os_name="$(uname -s)" +cpu_name="$(uname -m)" +case "$os_name:$cpu_name" in + Linux:x86_64|Linux:amd64) platform=linux; arch=x64; archive_ext=tar.xz; checksum="$NODE_LINUX_X64_SHA256" ;; + Linux:aarch64|Linux:arm64) platform=linux; arch=arm64; archive_ext=tar.xz; checksum="$NODE_LINUX_ARM64_SHA256" ;; + Darwin:x86_64|Darwin:amd64) platform=darwin; arch=x64; archive_ext=tar.gz; checksum="$NODE_DARWIN_X64_SHA256" ;; + Darwin:arm64|Darwin:aarch64) platform=darwin; arch=arm64; archive_ext=tar.gz; checksum="$NODE_DARWIN_ARM64_SHA256" ;; + *) + echo "Unsupported project runtime platform: $os_name/$cpu_name (use Node $NODE_VERSION manually on this host)." >&2 + exit 2 + ;; +esac + +archive_name="node-v${NODE_VERSION}-${platform}-${arch}.${archive_ext}" +archive_path="$runtime_root/downloads/$archive_name" +runtime_dir="$runtime_root/node-v${NODE_VERSION}-${platform}-${arch}" + +if [ -x "$runtime_dir/bin/node" ] && [ "$($runtime_dir/bin/node --version)" = "v$NODE_VERSION" ]; then + printf '%s\n' "$runtime_dir" + exit 0 +fi + +if [ -e "$archive_path" ]; then + if command -v sha256sum >/dev/null 2>&1; then + actual_checksum="$(sha256sum "$archive_path" | awk '{print $1}')" + else + actual_checksum="$(shasum -a 256 "$archive_path" | awk '{print $1}')" + fi + if [ "$actual_checksum" != "$checksum" ]; then + rm -f "$archive_path" + fi +fi + +if [ ! -e "$archive_path" ]; then + temp_archive="$archive_path.part" + rm -f "$temp_archive" + if ! command -v curl >/dev/null 2>&1; then + echo "curl is required to bootstrap the project-local Node runtime." >&2 + exit 2 + fi + curl --fail --location --retry 3 --output "$temp_archive" "$NODE_DIST_BASE/$archive_name" + if command -v sha256sum >/dev/null 2>&1; then + actual_checksum="$(sha256sum "$temp_archive" | awk '{print $1}')" + else + actual_checksum="$(shasum -a 256 "$temp_archive" | awk '{print $1}')" + fi + if [ "$actual_checksum" != "$checksum" ]; then + rm -f "$temp_archive" + echo "SHA-256 verification failed for $archive_name." >&2 + exit 3 + fi + mv "$temp_archive" "$archive_path" +fi + +if command -v sha256sum >/dev/null 2>&1; then + actual_checksum="$(sha256sum "$archive_path" | awk '{print $1}')" +else + actual_checksum="$(shasum -a 256 "$archive_path" | awk '{print $1}')" +fi +if [ "$actual_checksum" != "$checksum" ]; then + echo "Cached Node archive checksum does not match the pinned value." >&2 + exit 3 +fi + +extract_dir="$(mktemp -d "$runtime_root/extract.XXXXXX")" +cleanup() { rm -rf "$extract_dir"; } +trap cleanup EXIT +tar -xf "$archive_path" -C "$extract_dir" +extracted_dir="$extract_dir/node-v${NODE_VERSION}-${platform}-${arch}" +if [ ! -x "$extracted_dir/bin/node" ]; then + echo "Node archive did not contain the expected executable." >&2 + exit 4 +fi +rm -rf "$runtime_dir" +mv "$extracted_dir" "$runtime_dir" +if [ "$($runtime_dir/bin/node --version)" != "v$NODE_VERSION" ]; then + echo "Bootstrapped Node version is not v$NODE_VERSION." >&2 + exit 4 +fi +printf '%s\n' "$runtime_dir" diff --git a/scripts/check-runtime.mjs b/scripts/check-runtime.mjs new file mode 100644 index 0000000..6318509 --- /dev/null +++ b/scripts/check-runtime.mjs @@ -0,0 +1,9 @@ +const expected = process.env.BITBYBIT_NODE_VERSION ?? "22.23.2"; +const actual = process.versions.node; + +if (actual !== expected) { + console.error(`Project runtime mismatch: expected Node ${expected}, got ${actual}. Use ./npmw or ./nodew.`); + process.exit(1); +} + +console.log(`Project runtime OK: Node ${actual}`);