cjr Developer Guide
cjr is implemented in Rust. A single Cargo package provides the CLI and library. The user interface is limited to program execution, inspect, list and cache clean.
Update both language editions of the user guide when command behavior changes. Prefer the standard library and maintained dependencies for general infrastructure. Version metadata, official asset selection, cjv compatibility and cache policy belong to cjr.
The repository contains source code, ordinary tests and maintained documentation. Keep research directories, downloaded SDKs, probes, audit or acceptance reports and generated output in ignored local directories.
Report problems and suggestions through GitHub Issues. Read recent commit messages before committing and follow the existing style.
Build and test
Requirements
Use Rust 1.89 or newer, a C compiler and CMake. Windows requires MSVC. The TLS dependency aws-lc compiles native code.
cargo build --release --locked
The executable is target/release/cjr, or target/release/cjr.exe on Windows. Cargo.lock pins the dependency graph; commit it together with dependency changes.
Routine checks
cargo fmt --all -- --check
cargo clippy --all-targets --locked -- -D warnings
cargo test --all-targets --locked
Tests cover binary parsing, CLI arguments, download failures, cache lifecycle and process state. HTTP scenarios use mockito; arbitrary-input tests use proptest. Ordinary tests use local fixtures and mock services.
CI runs these checks and release builds on Windows, Linux, macOS ARM64 and Intel. A separate Linux job checks the minimum Rust version. The website job builds the landing page and documentation.
Implementation rules
- Keep FFI and necessary unsafe operations inside the private
platformmodule, behind safe interfaces with documented preconditions. - Hold
StoreTransactionfor cache mutations and usage registration. Keep cjv-owned files intact. - Use backon for retries, walkdir for directory traversal and object for binary formats.
- Retain usage records when descendant liveness is uncertain. Preserve the application’s exit status even when cleanup fails.
Architecture
Modules
| Module | Responsibility |
|---|---|
app, cli | Command parsing and orchestration |
inspect | PE, ELF and Mach-O version and dependency metadata |
catalog | Exact SDK version and platform matching in official manifests |
cjv | cjv configuration, archives and managed installations |
provision | Downloads, verification and selective extraction |
store | Immutable runtime snapshots, usage records and cleanup |
loader | Host compatibility, search paths and dependencies |
process | Launch, signals, exit status and descendant supervision |
platform | Native operations behind safe interfaces |
Launch flow
cjr reads metadata and checks host architecture and dependencies. When a runtime is needed, it selects an exact official asset with a checksum, reuses local content or downloads an archive, then extracts and publishes a runtime snapshot. After final dependency checks, it registers usage, releases the storage lock and launches the program.
The SDK version comes from PE .cjsdkv, ELF .cjmetadata or Mach-O __CJ_METADATA,__cjsdkversion. It identifies the specific SDK release used for asset selection. Parsers validate bounds and reject unknown layouts.
Mach-O chained fixups are decoded by object; inspect::macho::fixups maps rebases into the version table. Supported formats are DYLD_CHAINED_PTR_64, 64_OFFSET, ARM64E, ARM64E_USERLAND and ARM64E_USERLAND24, including authenticated pointers. Inspection decodes pointer bit fields through static file reads. Version entries accept rebase references to strings within the same file. Traversal has a work limit and checks file-to-virtual-address mappings, page boundaries and duplicate entries.
Cache lifecycle
StoreTransaction holds an exclusive storage lock for metadata, preparation, publication, usage registration and cleanup. Initial preparation of different versions is serialized. The lock is released while the application runs.
Runtimes live under runtimes/<bundle-id>/ with receipt.json and payload/. The manifest records content hashes, permissions and link targets. Publication uses a same-volume rename. Published runtimes are immutable and verified before each use.
reqwest performs downloads and backon manages retries. After SHA-256 verification, extraction selects runtime directories and license notices, rejecting unsafe paths, duplicate entries, special files and links escaping the payload.
cjv installation reads acquire .lifecycle.lock before .lifecycle.lookup.lock. Archive-derived snapshots are marked archive-verified; managed-installation snapshots are cjv-managed. Installation records identify cjv-managed sources; archive checksums establish byte-for-byte equality with official archives.
Loading and supervision
Environment changes apply only to the child process. Arguments pass directly to the process API as OsString values. Windows checks application-directory DLL shadowing, Linux checks RPATH/RUNPATH and preload conflicts, and macOS expands dyld path tokens and rejects recognized restricted programs.
Windows uses Job Objects and Linux uses a subreaper. Unix forwards termination signals and restores terminal foreground ownership. Usage records are retained while descendant exit states await confirmation; records from a previous boot on the same host can be reclaimed.
Supervision changes process-wide state and permits only one supervisor per process. Embedding hosts must coordinate process-wide signal dispositions. The storage trust boundary is the current user’s local filesystem: processes with the same UID can modify it, and hosts must separately accommodate network-filesystem locking semantics.
Documentation and website
The site follows cjv’s structure: web/ contains the landing page, while docs/user-guide/ and docs/dev-guide/ contain the two manuals. Each book uses src/ for Chinese and en/ for English.
The landing page uses React, TypeScript and Vite. Radix Tabs handles installation method switching, and react-i18next provides translations. React prerenders both languages during the build, and the browser hydrates those pages for interaction. Fonts are bundled locally. mdBook generates the manuals.
Local development
Install Node.js 22.22.2+, 24.15+ or 26+, npm and mdBook 0.5.4. The current Node.js LTS is recommended.
cd web
npm ci
npm run dev
Frontend source layout:
| Path | Responsibility |
|---|---|
src/App.tsx | Page composition |
src/components/ | Navigation, installation methods, downloads, documentation links and command copying |
src/config.ts | Release assets and installation commands |
src/i18n/ | Translation types, language detection and instance initialization |
src/locales/ | Chinese and English translations |
scripts/ | Version loading, React prerendering, mdBook builds and navigation |
Use npm run typecheck for strict TypeScript checks, npm run lint for code checks and npm run format to apply formatting. npm run test:unit runs interaction tests with Vitest and Testing Library.
To preview the Chinese user guide separately:
mdbook serve docs/user-guide
Build the full site
cd web
npm run build
npm run preview
The repository-root dist/ directory contains the landing page and all four books:
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/
The default base path is /. For a subpath deployment such as GitHub Pages at /cjr/, set SITE_BASE=/cjr/ when building. Landing-page links and assets share that prefix.
Upload the whole dist/ directory. Keep node_modules/, dist/, .edgeone/ and mdBook book/ output in ignored local directories.
Update both languages and their SUMMARY.md files when editing documentation. Installation instructions and download buttons must point to published binaries or installer scripts.
Languages and versions
Landing-page messages live in web/src/locales/zh-CN.json and en.json, managed by i18next. Explicit language URLs take precedence, followed by the legacy ?lang= query, a saved preference and the browser language. Other languages fall back to English.
The build generates complete HTML at /zh-CN/ and /en/ using the same React components as the browser. Static HTML provides download and documentation links. Each language has an independent translation instance, and hydration uses the page’s locale. The root path redirects to the preferred language page when needed. Titles, descriptions, accessible labels and copy feedback use translation resources. The download version comes from Cargo.toml, matching the binary.
Both documentation languages use matching chapter filenames. The build adds language links to each chapter. Switching keeps the chapter and drops heading anchors that differ between translations. Home links preserve the selected language. Chinese documentation toolbar translations live in web/src/locales/book-zh-CN.json.
npm test runs Biome checks, component interaction tests, TypeScript checks, the complete site build and output tests. Tests cover keyboard tab navigation, clipboard success and failure feedback, bilingual hydration, language precedence, downloads and chapter switching. Add messages to both languages; missing translation keys fail the build. Use SITE_BASE=/cjr/ npm test to check a subpath deployment. CI and production deployment use this same command.
Production deployment
The website at cjr.zxilly.com runs on EdgeOne Makers. The cjr-web project uses a Tencent Cloud China-site account with global coverage, including mainland China.
.github/workflows/website.yml builds, tests and uploads the complete site when website, documentation, version or deployment workflow files change on master. It also supports manual runs. The release workflow calls it after publishing a release to update the version and download links. Production deployments run serially; each upload completes before the next deployment starts.
GitHub repository settings:
| Type | Name | Value |
|---|---|---|
| Variable | EDGEONE_PROJECT_NAME | cjr-web |
| Secret | EDGEONE_API_TOKEN | China-site Makers API token |
Bind cjr.zxilly.com to production in Makers, then add the CNAME target provided by the console to Cloudflare with DNS only enabled. Makers manages the certificate. Deployments reuse the existing DNS and domain binding settings.
To deploy an already built site manually:
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
Deployment runs are listed in GitHub Actions. To roll back, select an earlier successful production deployment in Makers.
Releasing
package.version in Cargo.toml is the version source. Clap embeds it at compile time; cjr --version and cjr -V report the value stored in the binary.
Publish a version
- Update the package version in
Cargo.tomlandCargo.lock, the website package version and installation examples in both languages. - Run formatting, Clippy, tests and
npm testinweb/. Commit and push the changes. - Create a matching stable tag, such as
git tag -a v0.0.1 -m "cjr v0.0.1", then rungit push origin v0.0.1.
release.yml checks that the tag matches the package and calls the normal CI workflow. Once checks pass, it creates a draft release. taiki-e/upload-rust-binary-action builds and uploads four platform archives and their SHA-256 files:
- 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.
Archives are named cjr-v<version>-<target> and contain the executable and both READMEs. The minimum macOS version is 13.0. Each release binary is run with --version. Once all jobs succeed, softprops/action-gh-release publishes the draft. Failed jobs leave it unpublished and can be rerun in Actions. Keep published tags fixed and ship subsequent fixes in a new version.
Actions are pinned to full commit SHAs and tracked by Dependabot. The workflow uses GitHub’s GITHUB_TOKEN, granting write access only to jobs that create, upload or publish the release.
Static linking
.cargo/config.toml enables crt-static for Windows MSVC and Linux musl. Windows embeds the C/C++ runtime while retaining Windows system DLL imports. Linux release binaries use fully static linking, with empty dynamic dependency and ELF interpreter entries. Rust dependencies and bundled native libraries are linked statically. macOS uses system libraries under /usr/lib/ and /System/Library/, with libSystem and system frameworks supplied by the operating system.
The release workflow reads each final binary’s dependency table and rejects a dynamic Windows CRT, any Linux dynamic dependency, or non-system macOS libraries. This policy applies to cjr itself; Cangjie applications still use their matching dynamic runtime.