cjr 开发指南
cjr 使用 Rust 实现。一个 Cargo package 同时包含 CLI 和 library,用户接口限定为程序执行、inspect、list 和 cache clean。
修改命令行为时,同步更新中英文用户手册。通用能力优先使用标准库或维护中的依赖;版本识别、官方资产选择、cjv 适配和缓存策略由项目自身实现。
仓库保存源码、常规测试和可维护的文档。研究目录、SDK 下载、探测脚本、审计或验收报告及构建产物保留在本地忽略目录中。
问题和改进建议可以提交到 GitHub Issues。提交前读取最近的 commit 信息,保持仓库现有风格。
构建与测试
环境
需要 Rust 1.89 或更新版本、C 编译器和 CMake。Windows 使用 MSVC。TLS 依赖中的 aws-lc 会编译本地代码。
cargo build --release --locked
可执行文件位于 target/release/cjr,Windows 为 target/release/cjr.exe。Cargo.lock 固定完整依赖图;更新依赖时同时提交锁文件。
常规检查
cargo fmt --all -- --check
cargo clippy --all-targets --locked -- -D warnings
cargo test --all-targets --locked
测试覆盖二进制解析、命令参数、下载错误、缓存生命周期和进程状态。HTTP 场景使用 mockito,任意输入测试使用 proptest。常规测试使用本地夹具和模拟服务。
CI 在 Windows、Linux、macOS ARM64 和 Intel 上运行这些检查并构建 release,另在 Linux 检查最低 Rust 版本。网站任务构建 landing 页和文档。
实现约束
- 平台 FFI 和必要的 unsafe 集中在私有
platform模块,通过安全接口供业务模块调用,并说明调用前提。 - 缓存变更和使用登记必须持有
StoreTransaction;cjv 所有的文件保持原样。 - 下载退避使用 backon,遍历目录使用 walkdir,二进制解析使用 object。
- 派生进程退出状态待确认时保留使用记录。清理失败时仍返回目标程序的退出状态。
架构
模块边界
| 模块 | 职责 |
|---|---|
app、cli | 命令解析和执行编排 |
inspect | PE、ELF、Mach-O 的版本与依赖解析 |
catalog | 通过官方清单匹配完整 SDK 版本与平台 |
cjv | 读取 cjv 配置、归档和受管安装 |
provision | 下载、校验和选择性解包 |
store | 不可变运行库快照、使用记录与清理 |
loader | 宿主兼容性、加载路径和依赖检查 |
process | 启动、信号、退出状态与子进程监督 |
platform | 必要的原生调用及其安全封装 |
运行流程
读取程序元数据后,cjr 检查宿主架构和依赖。需要运行库时,根据完整 SDK 版本选择具有官方摘要的资产,复用本地内容或下载归档,提取并发布运行库快照。最终依赖检查通过后登记使用记录,释放存储锁,再启动程序。
SDK 版本来自 PE .cjsdkv、ELF .cjmetadata 或 Mach-O __CJ_METADATA,__cjsdkversion。它标识具体的 SDK 发行版本,作为发行包的选择依据。所有解析必须校验边界,未知布局返回错误。
Mach-O 的 chained fixups 由 object 解析,inspect::macho::fixups 将 rebase 结果映射到版本表。支持 DYLD_CHAINED_PTR_64、64_OFFSET、ARM64E、ARM64E_USERLAND 和 ARM64E_USERLAND24,包括认证指针。解析以静态文件读取方式解码指针位字段;版本条目仅接受指向本文件字符串的 rebase 引用。链遍历有工作量上限,并检查文件与虚拟地址映射、页边界和重复条目。
缓存生命周期
StoreTransaction 持有存储排他锁。元数据、准备、发布、使用登记和清理通过同一把锁协调。各版本的首次准备串行执行,启动程序前释放锁。
运行库发布到 runtimes/<bundle-id>/,包含 receipt.json 和 payload/。内容清单记录文件摘要、权限和链接目标。发布通过同卷 rename 完成;已发布运行库保持原样,每次使用前重新校验。
下载由 reqwest 执行,重试由 backon 管理。归档校验 SHA-256 后,只提取 runtime 目录和许可通知;拒绝越界路径、重复条目、特殊文件及逃出 payload 的链接。
cjv 受管安装读取按 .lifecycle.lock、.lifecycle.lookup.lock 的顺序加锁。使用归档的来源标为 archive-verified,受管安装快照标为 cjv-managed。安装记录标识 cjv 管理的来源;与官方归档逐字节一致性的验证以归档摘要为准。
加载和进程
环境修改只作用于子进程。参数通过 OsString 直接传给进程接口。Windows 检查应用目录 DLL 遮蔽,Linux 检查 RPATH/RUNPATH 和预加载冲突,macOS 展开 dyld 的路径占位符并拒绝已识别的受限程序。
Windows 使用 Job Object,Linux 使用 subreaper。Unix 转发终止信号并恢复前台终端。派生进程退出状态待确认时保留运行库使用记录;同一主机重启后可以回收旧会话记录。
监督涉及进程级状态,同一进程内只允许一个监督实例。嵌入该 library 的宿主需协调进程级信号 disposition。缓存的信任边界是当前用户的本地文件系统;同一 UID 的进程可以修改缓存,网络文件系统的锁语义需由宿主另行适配。
文档与网站
网站沿用 cjv 的组织方式:web/ 是 landing 页,docs/user-guide/ 和 docs/dev-guide/ 分别维护用户手册与开发指南。每本书的 src/ 为中文,en/ 为英文。
landing 页使用 React、TypeScript 和 Vite。安装方式切换由 Radix Tabs 处理,翻译通过 react-i18next 提供。构建时由 React 预渲染双语 HTML,浏览器通过 hydration 接管交互。字体随站点打包,文档由 mdBook 生成。
本地开发
需要 Node.js 22.22.2+、24.15+ 或 26+,以及 npm 和 mdBook 0.5.4。建议使用当前 Node.js LTS。
cd web
npm ci
npm run dev
前端源码按职责组织:
| 路径 | 职责 |
|---|---|
src/App.tsx | 页面组合 |
src/components/ | 导航、安装方式、下载与文档入口、命令复制 |
src/config.ts | 发行资产和安装命令 |
src/i18n/ | 翻译类型、语言检测和实例初始化 |
src/locales/ | 中英文翻译资源 |
scripts/ | 版本读取、React 预渲染、mdBook 构建与导航 |
npm run typecheck 执行严格 TypeScript 检查;npm run lint 和 npm run format 分别检查和整理代码。npm run test:unit 运行 Vitest 与 Testing Library 交互测试。
单独预览中文手册:
mdbook serve docs/user-guide
构建完整站点
cd web
npm run build
npm run preview
构建输出位于根目录 dist/,包含首页和四本书:
dist/
index.html
zh-CN/index.html
en/index.html
assets/
book/user-guide/zh-CN/
book/user-guide/en/
book/dev-guide/zh-CN/
book/dev-guide/en/
默认部署在域名根路径。在子路径部署时,例如 GitHub Pages 的 /cjr/,设置 SITE_BASE=/cjr/ 后构建。首页链接和静态资源将使用同一前缀。
部署时上传整个 dist/。node_modules/、dist/、.edgeone/ 和 mdBook 的 book/ 输出保留在本地忽略目录中。
修改文档时同步更新两种语言和各自的 SUMMARY.md。安装说明和下载按钮应指向已发布的安装脚本或二进制。
语言与版本
首页文案位于 web/src/locales/zh-CN.json 和 en.json,由 i18next 处理。显式语言 URL 优先,其次是旧链接的 ?lang=、已保存的选择和浏览器语言。其他语言回退到英文。
构建时使用与浏览器相同的 React 组件生成 /zh-CN/ 和 /en/ 的完整 HTML,下载和文档链接由静态 HTML 提供。每种语言独立初始化翻译实例,hydration 使用对应页面的语言。根路径按语言偏好跳转到相应语言页。页面标题、搜索摘要、无障碍标签和复制提示均使用翻译资源。下载版本从 Cargo.toml 读取,与二进制保持一致。
两种语言的文档使用相同章节文件名。构建过程为每章添加语言链接,切换时保留章节、移除各语言特有的标题锚点;首页链接保持所选语言。中文文档工具栏翻译位于 web/src/locales/book-zh-CN.json。
npm test 依次执行 Biome 检查、组件交互测试、TypeScript 检查、完整站点构建和产物测试。测试覆盖 tab 键盘操作、复制成功与失败反馈、双语 hydration、语言优先级、下载链接和章节切换。新增文案必须同时补齐两种语言,缺少翻译键会使构建失败。可以用 SITE_BASE=/cjr/ npm test 验证子路径部署。CI 和生产部署使用同一套命令。
生产部署
官网为 cjr.zxilly.com,托管在 EdgeOne Makers。项目 cjr-web 使用腾讯云中国站账户,部署区域为全球(含中国大陆)。
.github/workflows/website.yml 在 master 的网站、文档、版本或部署工作流变更后自动构建、测试并上传完整站点,也支持手动触发。发布工作流在 Release 正式发布后调用它,更新网站的版本和下载链接。生产部署串行执行,每次上传完成后再开始下一次部署。
GitHub 仓库配置:
| 类型 | 名称 | 内容 |
|---|---|---|
| Variable | EDGEONE_PROJECT_NAME | cjr-web |
| Secret | EDGEONE_API_TOKEN | 中国站 Makers API Token |
在 Makers 中绑定生产域名 cjr.zxilly.com,再按控制台提供的目标地址配置 Cloudflare CNAME,使用 DNS only。证书由 Makers 管理。DNS 和域名绑定配置一次后持续沿用。
手动部署已构建的站点:
npx --yes edgeone@1.6.47 login --site china --token "$EDGEONE_API_TOKEN"
npx --yes edgeone@1.6.47 makers deploy ./dist \
--name cjr-web --area global --env production --skip-ai-gateway-sync
部署记录见 GitHub Actions。需要回退时,在 Makers 的部署列表中选择先前成功的生产部署。
发布版本
版本号由 Cargo.toml 的 package.version 定义。Clap 在编译时将它嵌入程序,cjr --version 和 cjr -V 均输出该版本;版本信息随二进制一同分发。
发布步骤
- 更新
Cargo.toml和Cargo.lock中的包版本,同步网站包版本、中英文安装示例。 - 运行格式化、Clippy、测试及
web/下的npm test,提交并推送改动。 - 创建与包版本一致的稳定标签,例如
git tag -a v0.0.1 -m "cjr v0.0.1",再运行git push origin v0.0.1。
release.yml 首先检查标签与包版本,再调用常规 CI。通过后创建草稿 Release,使用 taiki-e/upload-rust-binary-action 构建、打包并上传四个平台的程序及 SHA-256 文件:
- Windows x64:
x86_64-pc-windows-msvc,ZIP。 - Linux x64:
x86_64-unknown-linux-musl,tar.gz。 - macOS Apple Silicon:
aarch64-apple-darwin,tar.gz。 - macOS Intel:
x86_64-apple-darwin,tar.gz。
归档命名为 cjr-v<版本>-<target>,包含可执行文件及中英文 README。macOS 最低版本为 13.0。发布任务逐个运行二进制的 --version,全部成功后由 softprops/action-gh-release 将草稿公开。失败时草稿保持未发布,可在 Actions 中重跑失败任务。已公开版本的标签保持固定,后续修复使用新版本号。
Action 固定到完整提交 SHA,并由 Dependabot 跟踪更新。工作流使用 GitHub 提供的 GITHUB_TOKEN,只在 Release 创建、上传和公开任务中授予写权限。
静态链接
.cargo/config.toml 为 Windows MSVC 和 Linux musl 目标启用 crt-static。Windows 将 C/C++ 运行时链接进程序,仍依赖 Windows 系统 DLL;Linux 产物采用完全静态链接,动态依赖表和 ELF 解释器项均为空。Rust 依赖与可打包的原生依赖静态链接。macOS 使用 /usr/lib/、/System/Library/ 下的系统库,由操作系统提供 libSystem 和系统框架。
发布工作流读取最终二进制的依赖表:拒绝 Windows 动态 CRT、Linux 动态依赖和 macOS 非系统库。此策略针对 cjr 本身;被启动的仓颉程序仍使用匹配的动态运行库。